Skip to content

Commit 7358a6c

Browse files
committed
refactor(allocator, linter): add ref counter to FixedSizeAllocator
1 parent 33ce529 commit 7358a6c

File tree

4 files changed

+30
-33
lines changed

4 files changed

+30
-33
lines changed

crates/oxc_allocator/src/generated/assert_layouts.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ use crate::*;
99

1010
#[cfg(target_pointer_width = "64")]
1111
const _: () = {
12-
// Padding: 3 bytes
12+
// Padding: 0 bytes
1313
assert!(size_of::<FixedSizeAllocatorMetadata>() == 16);
1414
assert!(align_of::<FixedSizeAllocatorMetadata>() == 8);
1515
assert!(offset_of!(FixedSizeAllocatorMetadata, id) == 8);
1616
assert!(offset_of!(FixedSizeAllocatorMetadata, alloc_ptr) == 0);
17-
assert!(offset_of!(FixedSizeAllocatorMetadata, is_double_owned) == 12);
17+
assert!(offset_of!(FixedSizeAllocatorMetadata, ref_count) == 12);
1818
};
1919

2020
#[cfg(target_pointer_width = "32")]
2121
const _: () = {
22-
// Padding: 3 bytes
22+
// Padding: 0 bytes
2323
assert!(size_of::<FixedSizeAllocatorMetadata>() == 12);
2424
assert!(align_of::<FixedSizeAllocatorMetadata>() == 4);
2525
assert!(offset_of!(FixedSizeAllocatorMetadata, id) == 4);
2626
assert!(offset_of!(FixedSizeAllocatorMetadata, alloc_ptr) == 0);
27-
assert!(offset_of!(FixedSizeAllocatorMetadata, is_double_owned) == 8);
27+
assert!(offset_of!(FixedSizeAllocatorMetadata, ref_count) == 8);
2828
};
2929

3030
#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]

crates/oxc_allocator/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,15 @@ pub use pool::{AllocatorGuard, AllocatorPool};
121121
)))]
122122
#[allow(missing_docs, clippy::missing_safety_doc, clippy::unused_self, clippy::allow_attributes)]
123123
mod dummies {
124-
use std::{ptr::NonNull, sync::atomic::AtomicBool};
124+
use std::{ptr::NonNull, sync::atomic::AtomicU32};
125125

126126
use super::Allocator;
127127

128128
#[doc(hidden)]
129129
pub struct FixedSizeAllocatorMetadata {
130130
pub id: u32,
131131
pub alloc_ptr: NonNull<u8>,
132-
pub is_double_owned: AtomicBool,
132+
pub ref_count: AtomicU32,
133133
}
134134

135135
#[doc(hidden)]

crates/oxc_allocator/src/pool_fixed_size.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
ptr::NonNull,
66
sync::{
77
Mutex,
8-
atomic::{AtomicBool, AtomicU32, Ordering},
8+
atomic::{AtomicU32, Ordering},
99
},
1010
};
1111

@@ -113,15 +113,14 @@ pub struct FixedSizeAllocatorMetadata {
113113
pub id: u32,
114114
/// Pointer to start of original allocation backing the `FixedSizeAllocator`
115115
pub alloc_ptr: NonNull<u8>,
116-
/// `true` if both Rust and JS currently hold references to this `FixedSizeAllocator`.
116+
/// Number of references to this `FixedSizeAllocator`.
117117
///
118-
/// * `false` initially.
119-
/// * Set to `true` when buffer is shared with JS.
120-
/// * When JS garbage collector collects the buffer, set back to `false` again.
121-
/// Memory will be freed when the `FixedSizeAllocator` is dropped on Rust side.
122-
/// * Also set to `false` if `FixedSizeAllocator` is dropped on Rust side.
123-
/// Memory will be freed in finalizer when JS garbage collector collects the buffer.
124-
pub is_double_owned: AtomicBool,
118+
/// * 1 initially.
119+
/// * Incremented when buffer is shared with JS.
120+
/// * Decremented when JS garbage collector collects the buffer.
121+
/// * Decremented when `FixedSizeAllocator` is dropped on Rust side.
122+
/// * Memory is freed when counter reaches 0.
123+
pub ref_count: AtomicU32,
125124
}
126125

127126
// What we ideally want is an allocation 2 GiB in size, aligned on 4 GiB.
@@ -260,8 +259,7 @@ impl FixedSizeAllocator {
260259

261260
// Write `FixedSizeAllocatorMetadata` to after space reserved for `RawTransferMetadata`,
262261
// which is after the end of the allocator chunk
263-
let metadata =
264-
FixedSizeAllocatorMetadata { alloc_ptr, id, is_double_owned: AtomicBool::new(false) };
262+
let metadata = FixedSizeAllocatorMetadata { alloc_ptr, id, ref_count: AtomicU32::new(1) };
265263
// SAFETY: `FIXED_METADATA_OFFSET` is `FIXED_METADATA_SIZE_ROUNDED` bytes before end of
266264
// the allocation, so there's space for `FixedSizeAllocatorMetadata`.
267265
// It's sufficiently aligned for `FixedSizeAllocatorMetadata`.
@@ -304,11 +302,11 @@ impl Drop for FixedSizeAllocator {
304302
}
305303
}
306304

307-
/// Deallocate memory backing a `FixedSizeAllocator` if it's not double-owned
305+
/// Deallocate memory backing a `FixedSizeAllocator` if it's not multiply-referenced
308306
/// (both owned by a `FixedSizeAllocator` on Rust side *and* held as a buffer on JS side).
309307
///
310-
/// If it is double-owned, don't deallocate the memory but set the flag that it's no longer double-owned
311-
/// so next call to this function will deallocate it.
308+
/// If it is multiply-referenced, don't deallocate the memory, but decrement the ref counter.
309+
/// A later call to this function will deallocate the memory once the ref counter reaches 0.
312310
///
313311
/// # SAFETY
314312
///
@@ -326,22 +324,20 @@ pub unsafe fn free_fixed_size_allocator(metadata_ptr: NonNull<FixedSizeAllocator
326324
// `&FixedSizeAllocatorMetadata` ref only lives until end of this block.
327325
let metadata = unsafe { metadata_ptr.as_ref() };
328326

329-
// * If `is_double_owned` is already `false`, then one of:
330-
// 1. The `Allocator` was never sent to JS side, or
331-
// 2. The `FixedSizeAllocator` was already dropped on Rust side, or
332-
// 3. Garbage collector already collected it on JS side.
333-
// We can deallocate the memory.
327+
// Decrement the ref count.
334328
//
335-
// * If `is_double_owned` is `true`, set it to `false` and exit.
336-
// Memory will be freed when `FixedSizeAllocator` is dropped on Rust side
337-
// or JS garbage collector collects the buffer.
329+
// If ref count is not 1 (before decrementing), then the memory is still in use elsewhere,
330+
// because either:
331+
// * `FixedSizeAllocator` has not yet been dropped on Rust side, or
332+
// * JS garbage collector has not yet collected the buffer on JS side.
333+
// Therefore we cannot free the memory yet. Exit.
338334
//
339335
// Maybe a more relaxed `Ordering` would be OK, but I (@overlookmotel) am not sure,
340336
// so going with `Ordering::SeqCst` to be on safe side.
341337
// Deallocation only happens at the end of the whole process, so it shouldn't matter much.
342338
// TODO: Figure out if can use `Ordering::Relaxed`.
343-
let is_double_owned = metadata.is_double_owned.swap(false, Ordering::SeqCst);
344-
if is_double_owned {
339+
let old_ref_count = metadata.ref_count.fetch_sub(1, Ordering::SeqCst);
340+
if old_ref_count != 1 {
345341
return;
346342
}
347343

napi/oxlint2/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ fn wrap_lint_file(cb: JsLintFileCb) -> ExternalLinterLintFileCb {
118118
///
119119
/// So only create a `Uint8Array` if it's not already sent to JS.
120120
///
121-
/// Whether the buffer has already been sent to JS is tracked by a flag in `FixedSizeAllocatorMetadata`,
122-
/// which is stored in memory backing the `Allocator`.
121+
/// Whether the buffer has already been sent to JS is tracked by a reference counter
122+
/// in `FixedSizeAllocatorMetadata`, which is stored in memory backing the `Allocator`.
123123
///
124124
/// # SAFETY
125125
/// `allocator` must have been created via `FixedSizeAllocator`
@@ -140,7 +140,8 @@ unsafe fn get_buffer(
140140

141141
// Get whether this buffer has already been sent to JS
142142
// TODO: Is `SeqCst` excessive here?
143-
let already_sent_to_js = metadata.is_double_owned.swap(true, Ordering::SeqCst);
143+
let old_ref_count = metadata.ref_count.swap(2, Ordering::SeqCst);
144+
let already_sent_to_js = old_ref_count > 1;
144145

145146
// If buffer has already been sent to JS, don't send it again
146147
if already_sent_to_js {

0 commit comments

Comments
 (0)