@@ -6,7 +6,7 @@ use crate::util::heap::gc_trigger::GCTrigger;
66use crate :: util:: options:: Options ;
77use crate :: MMTK ;
88
9- use atomic :: Atomic ;
9+ use std :: cell :: RefCell ;
1010use std:: sync:: atomic:: Ordering ;
1111use std:: sync:: Arc ;
1212
@@ -28,39 +28,51 @@ pub enum AllocationError {
2828 MmapOutOfMemory ,
2929}
3030
31- /// Behavior when an allocation fails, and a GC is expected.
32- #[ repr( u8 ) ]
33- #[ derive( Copy , Clone , Default , PartialEq , bytemuck:: NoUninit , Debug ) ]
34- pub enum OnAllocationFail {
35- /// Request the GC. This is the default behavior.
36- #[ default]
37- RequestGC ,
38- /// Instead of requesting GC, the allocation request returns with a failure value.
39- ReturnFailure ,
40- /// Instead of requesting GC, the allocation request simply overcommits the memory,
41- /// and return a valid result at its best efforts.
42- OverCommit ,
43- }
44-
45- impl OnAllocationFail {
46- pub ( crate ) fn allow_oom_call ( & self ) -> bool {
47- * self == Self :: RequestGC
48- }
49- pub ( crate ) fn allow_gc ( & self ) -> bool {
50- * self == Self :: RequestGC
51- }
52- pub ( crate ) fn allow_overcommit ( & self ) -> bool {
53- * self == Self :: OverCommit
54- }
55- }
56-
5731/// Allow specifying different behaviors with [`Allocator::alloc_with_options`].
5832#[ repr( C ) ]
59- #[ derive( Copy , Clone , Default , PartialEq , bytemuck :: NoUninit , Debug ) ]
33+ #[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
6034pub struct AllocationOptions {
61- /// When the allocation fails and a GC is originally expected, on_fail
62- /// allows a different behavior to avoid the GC.
63- pub on_fail : OnAllocationFail ,
35+ /// Whether over-committing is allowed at this allocation site. Over-committing means the
36+ /// allocation is allowed to go beyond the current heap size. But it is not guaranteed to
37+ /// succeed.
38+ ///
39+ /// **The default is `false`**.
40+ ///
41+ /// Note that regardless of the value of `allow_overcommit`, the allocation may trigger GC if
42+ /// the GC trigger considers it needed.
43+ pub allow_overcommit : bool ,
44+
45+ /// Whether the allocation is at a safepoint.
46+ ///
47+ /// **The default is `true`**.
48+ ///
49+ /// If `true`, the allocation is allowed to block for GC.
50+ ///
51+ /// If `false`, the allocation will immediately return a null address if the allocation cannot
52+ /// be satisfied without a GC.
53+ pub at_safepoint : bool ,
54+
55+ /// Whether the allocation is allowed to call [`Collection::out_of_memory`].
56+ ///
57+ /// **The default is `true`**.
58+ ///
59+ /// If `true`, the allocation will call [`Collection::out_of_memory`] when out of memory and
60+ /// return null.
61+ ///
62+ /// If `fasle`, the allocation will return null immediately when out of memory.
63+ pub allow_oom_call : bool ,
64+ }
65+
66+ /// The default value for `AllocationOptions` has the same semantics as calling [`Allocator::alloc`]
67+ /// directly.
68+ impl Default for AllocationOptions {
69+ fn default ( ) -> Self {
70+ Self {
71+ allow_overcommit : false ,
72+ at_safepoint : true ,
73+ allow_oom_call : true ,
74+ }
75+ }
6476}
6577
6678impl AllocationOptions {
@@ -69,6 +81,56 @@ impl AllocationOptions {
6981 }
7082}
7183
84+ /// A wrapper for [`AllocatorContext`] to hold a [`AllocationOptions`] that can be modified by the
85+ /// same mutator thread.
86+ ///
87+ /// All [`Allocator`] instances in `Allocators` share one `AllocationOptions` instance, and it will
88+ /// only be accessed by the mutator (via `Mutator::allocators`) or the GC worker (via
89+ /// `GCWorker::copy`) that owns it. Rust doesn't like multiple mutable references pointing to a
90+ /// shared data structure. We cannot use [`atomic::Atomic`] because `AllocationOptions` has
91+ /// multiple fields. We wrap it in a `RefCell` to make it internally mutable.
92+ ///
93+ /// Note: The allocation option is called every time [`Allocator::alloc_with_options`] is called.
94+ /// Because API functions should only be called on allocation slow paths, we believe that `RefCell`
95+ /// should be good enough for performance. If this is too slow, we may consider `UnsafeCell`. If
96+ /// that's still too slow, we should consider changing the API to make the allocation options a
97+ /// persistent per-mutator value, and allow the VM binding set its value via a new API function.
98+ struct AllocationOptionsHolder {
99+ alloc_options : RefCell < AllocationOptions > ,
100+ }
101+
102+ /// Strictly speaking, `AllocationOptionsHolder` isn't `Sync`. Two threads cannot set or clear the
103+ /// same `AllocationOptionsHolder` at the same time. However, both `Mutator` and `GCWorker` are
104+ /// `Send`, and both of which own `Allocators` and require its field `Arc<AllocationContext>` to be
105+ /// `Send`, which requires `AllocationContext` to be `Sync`, which requires
106+ /// `AllocationOptionsHolder` to be `Sync`. (Note that `Arc<T>` can be cloned and given to another
107+ /// thread, and Rust expects `T` to be `Sync`, too. But we never share `AllocationContext` between
108+ /// threads, but only between multiple `Allocator` instances within the same `Allocators` instance.
109+ /// Rust can't figure this out.)
110+ unsafe impl Sync for AllocationOptionsHolder { }
111+
112+ impl AllocationOptionsHolder {
113+ pub fn new ( alloc_options : AllocationOptions ) -> Self {
114+ Self {
115+ alloc_options : RefCell :: new ( alloc_options) ,
116+ }
117+ }
118+ pub fn set_alloc_options ( & self , options : AllocationOptions ) {
119+ let mut alloc_options = self . alloc_options . borrow_mut ( ) ;
120+ * alloc_options = options;
121+ }
122+
123+ pub fn clear_alloc_options ( & self ) {
124+ let mut alloc_options = self . alloc_options . borrow_mut ( ) ;
125+ * alloc_options = AllocationOptions :: default ( ) ;
126+ }
127+
128+ pub fn get_alloc_options ( & self ) -> AllocationOptions {
129+ let alloc_options = self . alloc_options . borrow ( ) ;
130+ * alloc_options
131+ }
132+ }
133+
72134pub fn align_allocation_no_fill < VM : VMBinding > (
73135 region : Address ,
74136 alignment : usize ,
@@ -180,7 +242,7 @@ pub(crate) fn assert_allocation_args<VM: VMBinding>(size: usize, align: usize, o
180242
181243/// The context an allocator needs to access in order to perform allocation.
182244pub struct AllocatorContext < VM : VMBinding > {
183- pub alloc_options : Atomic < AllocationOptions > ,
245+ alloc_options : AllocationOptionsHolder ,
184246 pub state : Arc < GlobalState > ,
185247 pub options : Arc < Options > ,
186248 pub gc_trigger : Arc < GCTrigger < VM > > ,
@@ -191,7 +253,7 @@ pub struct AllocatorContext<VM: VMBinding> {
191253impl < VM : VMBinding > AllocatorContext < VM > {
192254 pub fn new ( mmtk : & MMTK < VM > ) -> Self {
193255 Self {
194- alloc_options : Atomic :: new ( AllocationOptions :: default ( ) ) ,
256+ alloc_options : AllocationOptionsHolder :: new ( AllocationOptions :: default ( ) ) ,
195257 state : mmtk. state . clone ( ) ,
196258 options : mmtk. options . clone ( ) ,
197259 gc_trigger : mmtk. gc_trigger . clone ( ) ,
@@ -201,16 +263,15 @@ impl<VM: VMBinding> AllocatorContext<VM> {
201263 }
202264
203265 pub fn set_alloc_options ( & self , options : AllocationOptions ) {
204- self . alloc_options . store ( options, Ordering :: Relaxed ) ;
266+ self . alloc_options . set_alloc_options ( options) ;
205267 }
206268
207269 pub fn clear_alloc_options ( & self ) {
208- self . alloc_options
209- . store ( AllocationOptions :: default ( ) , Ordering :: Relaxed ) ;
270+ self . alloc_options . clear_alloc_options ( ) ;
210271 }
211272
212273 pub fn get_alloc_options ( & self ) -> AllocationOptions {
213- self . alloc_options . load ( Ordering :: Relaxed )
274+ self . alloc_options . get_alloc_options ( )
214275 }
215276}
216277
@@ -367,12 +428,6 @@ pub trait Allocator<VM: VMBinding>: Downcast {
367428 return result;
368429 }
369430
370- if result. is_zero ( )
371- && self . get_context ( ) . get_alloc_options ( ) . on_fail == OnAllocationFail :: ReturnFailure
372- {
373- return result;
374- }
375-
376431 if !result. is_zero ( ) {
377432 // Report allocation success to assist OutOfMemory handling.
378433 if !self
@@ -427,6 +482,17 @@ pub trait Allocator<VM: VMBinding>: Downcast {
427482 return result;
428483 }
429484
485+ // From here on, we handle the case that alloc_once failed.
486+ assert ! ( result. is_zero( ) ) ;
487+
488+ if !self . get_context ( ) . get_alloc_options ( ) . at_safepoint {
489+ // If the allocation is not at safepoint, it will not be able to block for GC. But
490+ // the code beyond this point tests OOM conditions and, if not OOM, try to allocate
491+ // again. Since we didn't block for GC, the allocation will fail again if we try
492+ // again. So we return null immediately.
493+ return Address :: ZERO ;
494+ }
495+
430496 // It is possible to have cases where a thread is blocked for another GC (non emergency)
431497 // immediately after being blocked for a GC (emergency) (e.g. in stress test), that is saying
432498 // the thread does not leave this loop between the two GCs. The local var 'emergency_collection'
0 commit comments