Skip to content

Commit

Permalink
clone_to_alloc
Browse files Browse the repository at this point in the history
  • Loading branch information
billythedummy committed Aug 29, 2023
1 parent 694e9db commit 438b67f
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED]

### Added

- `clone_to_alloc()` method to allow cloning of large `ConstLru`s without stack overflows

### Fixed

- `clear()` causing stack overflows for large `ConstLru`s
Expand Down
61 changes: 45 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,7 @@ impl<K, V, const CAP: usize, I: PrimInt + Unsigned> ConstLru<K, V, CAP, I> {
self.head = index;
}

/// Cleanup for drop impl
/// Drops keys and values.
/// Cleanup for drop impl. Drops keys and values.
/// Other fields should be all primitive types
fn drop_cleanup(&mut self) {
for (k, v) in IterMaybeUninit::new(self) {
Expand Down Expand Up @@ -482,23 +481,51 @@ impl<K: Ord, V, const CAP: usize, I: PrimInt + Unsigned> ConstLru<K, V, CAP, I>
}
}

impl<K: Clone, V: Clone, const CAP: usize, I: PrimInt + Unsigned> ConstLru<K, V, CAP, I> {
/// Clones the ConstLru to a region of allocated memory
///
/// # Safety
/// `dst` must point to uninitialized memory, since this
/// overwrites the data at `dst`
pub unsafe fn clone_to_alloc(&self, dst: *mut Self) {
addr_of_mut!((*dst).len).write(self.len);
addr_of_mut!((*dst).head).write(self.head);
addr_of_mut!((*dst).tail).write(self.tail);

// .write(self.nexts) result in stack overflow for large CAP, so use raw memmove
ptr::copy(
self.nexts.as_ptr(),
addr_of_mut!((*dst).nexts) as *mut I,
CAP,
);
ptr::copy(
self.prevs.as_ptr(),
addr_of_mut!((*dst).prevs) as *mut I,
CAP,
);
ptr::copy(
self.bs_index.as_ptr(),
addr_of_mut!((*dst).bs_index) as *mut I,
CAP,
);

for (index, k, v) in IterIndexed::new(self) {
let i = index.to_usize().unwrap();
addr_of_mut!((*dst).keys[i]).write(MaybeUninit::new(k.clone()));
addr_of_mut!((*dst).values[i]).write(MaybeUninit::new(v.clone()));
}
}
}

/// WARNING: this might result in runtime stack overflow errors for large `CAP`.
/// To clone a large `ConstLru`, use [`ConstLru::clone_to_alloc`]
impl<K: Clone, V: Clone, const CAP: usize, I: PrimInt + Unsigned> Clone for ConstLru<K, V, CAP, I> {
fn clone(&self) -> Self {
let mut res = Self {
len: self.len,
head: self.head,
tail: self.tail,
bs_index: self.bs_index,
nexts: self.nexts,
prevs: self.prevs,
keys: unsafe { MaybeUninit::uninit().assume_init() },
values: unsafe { MaybeUninit::uninit().assume_init() },
};
for (i, k, v) in IterIndexed::new(self) {
res.keys[i.to_usize().unwrap()].write(k.clone());
res.values[i.to_usize().unwrap()].write(v.clone());
let mut res: MaybeUninit<Self> = MaybeUninit::uninit();
unsafe {
self.clone_to_alloc(res.as_mut_ptr());
res.assume_init()
}
res
}
}

Expand Down Expand Up @@ -543,6 +570,8 @@ pub struct DuplicateKeysError<K>(
/// Assumes `entries` is in MRU -> LRU order.
///
/// Returns error if duplicate keys found.
///
/// WARNING: this might result in runtime stack overflow errors for large `CAP`.
impl<K: Ord, V, const CAP: usize, I: PrimInt + Unsigned> TryFrom<[(K, V); CAP]>
for ConstLru<K, V, CAP, I>
{
Expand Down
15 changes: 14 additions & 1 deletion tests/test_stack_overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::alloc::{alloc, Layout};

use const_lru::ConstLru;

type BigConstLru = ConstLru<usize, usize, 1_000_000>;
// ~400 MB
type BigConstLru = ConstLru<usize, usize, 10_000_000>;
const BIG_CONST_LRU_LAYOUT: Layout = Layout::new::<BigConstLru>();

/*
Expand Down Expand Up @@ -41,3 +42,15 @@ fn clear_doesnt_stack_overflow() {
c.clear();
assert!(c.insert(1, 2).is_none());
}

#[test]
#[cfg_attr(miri, ignore)]
fn clone_to_alloc_doesnt_stack_overflow() {
let c = boxed_big_const_lru();
let mut cloned = unsafe {
let new_alloc_ptr = alloc(BIG_CONST_LRU_LAYOUT) as *mut BigConstLru;
c.clone_to_alloc(new_alloc_ptr);
Box::from_raw(new_alloc_ptr)
};
assert!(cloned.insert(1, 2).is_none());
}

0 comments on commit 438b67f

Please sign in to comment.