Skip to content

Conversation

@Luro02
Copy link

@Luro02 Luro02 commented Oct 31, 2025

While looking into #24, I noticed some pieces of code in the bump allocator that looked wrong.

What has been changed?

  • The new code does not assume that the memory is initialized, before it was written to (I think the old one was okay too, but not sure)
  • Miri found this in the original code let ptr = t_buf.as_ptr() as *mut T; where a *const T is cast to *mut T
  • The old implementation took a reference to the memory and implicitly extended it to the lifetime of &self by the use of pointers, I made this explicit through references. Note that this might have been a wrong decision, miri isn't happy with my current impl (see pending CI), because of overlapping borrows. Will have to check if the original code had that issue too.
  • Added tests and updated the CI to run them
  • Added miri in CI to catch undefined behavior. Given that this crate can run on a regular host like linux, it seems sensible to use miri.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Nov 2, 2025

Nit: I'm not sure replacing

pub struct BumpBox<'a, T> {
    ptr: NonNull<T>,
    _allocator: core::marker::PhantomData<&'a ()>,
}

with

pub struct BumpBox<'a, T> {
    value: &'a mut T,
}

... is necessarily making the intent clearer.

However removing the destructor

impl<T> Drop for BumpBox<'_, T> {
    fn drop(&mut self) {
        // Safety: The pointer is valid and we own the data
        unsafe {
            self.ptr.as_ptr().drop_in_place();
        }
    }
}

... is absolutely NOK. Why did you do that?

Otherwise, I totally like having miri as part of the CI.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Nov 2, 2025

The new code does not assume that the memory is initialized, before it was written to (I think the old one was okay too, but not sure)

It is OK (in that it will never, ever lead to a program crash or anything like that - it is just a UB if you read from the uninitialized memory which was assumed initialized, but we don't do that, and even if we did, it would be "undefined behavior" but not a crash).

Miri found this in the original code let ptr = t_buf.as_ptr() as *mut T; where a *const T is cast to *mut T

Yeah. Maybe just let ptr = t_buf.as_mut_ptr() and be done with it?

@Luro02
Copy link
Author

Luro02 commented Nov 2, 2025

Nit: I'm not sure replacing

pub struct BumpBox<'a, T> {
    ptr: NonNull<T>,
    _allocator: core::marker::PhantomData<&'a ()>,
}

with

pub struct BumpBox<'a, T> {
    value: &'a mut T,
}

... is necessarily making the intent clearer.

However removing the destructor

impl<T> Drop for BumpBox<'_, T> {
    fn drop(&mut self) {
        // Safety: The pointer is valid and we own the data
        unsafe {
            self.ptr.as_ptr().drop_in_place();
        }
    }
}

... is absolutely NOK. Why did you do that?

Otherwise, I totally like having miri as part of the CI.

I think I misunderstood a part of the code, yeah &mut T isn't sensible that should be a pointer. I thought of it as a reference to the array and not as it having ownership, which it should have... I will undo those changes.

Copy link
Collaborator

@ivmarkov ivmarkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my two comments. Rest is fine.

src/bump.rs Outdated
const fn new() -> Self {
Self {
memory: MaybeUninit::uninit(),
memory: [const { MaybeUninit::uninit() }; N],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you confirmed that [const { MaybeUninit::uninit() }; N], would absolutely, positively NOT allocate on-stack and them move to the final destination?

To my understanding, only MaybeUninit::uninit() is guaranteed to have this property, regardless of the compiler optimization settings.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it out, and it worked fine, but based on my research, it seems like this is not always the case (seems to depend on compiler optimizations). Given that we are working with 1.77, the const expression is sadly unavailable.

@github-actions
Copy link

PR #25: Size comparison from cf4a089 to 8ccbff4

Full report (2 builds for light, light_eth)
platform target config section cf4a089 8ccbff4 change % change
light x86_64-unknown-linux-gnu infologs-optz-ltofat FLASH 2623816 2623312 -504 -0.0
RAM 70392 70392 0 0.0
light_eth x86_64-unknown-linux-gnu infologs-optz-ltofat FLASH 1858512 1858816 304 0.0
RAM 62576 62576 0 0.0

@ivmarkov
Copy link
Collaborator

Hm, miri seems unhappy with your changes? How was it before your changes?

@Luro02
Copy link
Author

Luro02 commented Nov 16, 2025

The CI failure is what I referenced with this comment:

Note that this might have been a wrong decision, miri isn't happy with my current impl (see pending CI), because of overlapping borrows. Will have to check if the original code had that issue too.

Here is what it outputs on master with the tests copied and the Cargo.toml adjusted to work with tests:

➜  rs-matter-stack git:(master) ✗ cargo miri test --features std
     Running unittests src/lib.rs (target/miri/x86_64-unknown-linux-gnu/debug/deps/rs_matter_stack-42303a557d0b917b)
warning: Miri does not support optimizations: the opt-level is ignored. The only effect of selecting a Cargo profile that enables optimizations (such as --release) is to apply its remaining settings, such as whether debug assertions and overflow checks are enabled.


running 2 tests
test bump::tests::test_multiple_concurrent_borrow ... error: Undefined Behavior: attempting a write access using <134984> at alloc233[0x10], but that tag only grants SharedReadOnly permission for this location
   --> src/bump.rs:128:17
    |
128 |                 ptr.write(object);
    |                 ^^^^^^^^^^^^^^^^^ this error occurs as part of an access at alloc233[0x10..0x18]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <134984> was created by a SharedReadOnly retag at offsets [0x10..0x18]
   --> src/bump.rs:127:27
    |
127 |                 let ptr = t_buf.as_ptr() as *mut T;
    |                           ^^^^^^^^^^^^^^
    = note: BACKTRACE (of the first span) on thread `bump::tests::te`:
    = note: inside closure at src/bump.rs:128:17: 128:34
    = note: inside closure at /home/lucas/.cargo/git/checkouts/rs-matter-2f17d53917d92169/270cb02/rs-matter/src/utils/sync/blocking.rs:81:17: 81:25
    = note: inside `<rs_matter::utils::sync::blocking::raw::StdRawMutex as embassy_sync::blocking_mutex::raw::RawMutex>::lock::<bump::BumpBox<'_, usize>, {closure@rs_matter::utils::sync::blocking::Mutex<rs_matter::utils::sync::blocking::raw::StdRawMutex, rs_matter::utils::cell::RefCell<bump::Inner<1024>>>::lock<bump::BumpBox<'_, usize>, {closure@src/bump.rs:104:25: 104:32}>::{closure#0}}>` at /home/lucas/.cargo/git/checkouts/rs-matter-2f17d53917d92169/270cb02/rs-matter/src/utils/sync/blocking.rs:249:17: 249:20
    = note: inside `rs_matter::utils::sync::blocking::Mutex::<rs_matter::utils::sync::blocking::raw::StdRawMutex, rs_matter::utils::cell::RefCell<bump::Inner<1024>>>::lock::<bump::BumpBox<'_, usize>, {closure@src/bump.rs:104:25: 104:32}>` at /home/lucas/.cargo/git/checkouts/rs-matter-2f17d53917d92169/270cb02/rs-matter/src/utils/sync/blocking.rs:78:13: 82:15
note: inside `bump::Bump::<1024, rs_matter::utils::sync::blocking::raw::StdRawMutex>::alloc::<usize>`
   --> src/bump.rs:104:9
    |
104 | /         self.inner.lock(|inner| {
105 | |             let mut inner = inner.borrow_mut();
106 | |
107 | |             let size = core::mem::size_of_val(&object);
...   |
139 | |         })
    | |__________^
note: inside `bump::tests::test_multiple_concurrent_borrow`
   --> src/bump.rs:22:9
    |
 22 |         $bump.alloc($obj, concat!(file!(), ":", line!()))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
258 |             all_boxes.push(alloc!(BUMP, i));
    |                            --------------- in this macro invocation
note: inside closure
   --> src/bump.rs:253:41
    |
252 |     #[test]
    |     ------- in this attribute macro expansion
253 |     fn test_multiple_concurrent_borrow() {
    |                                         ^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error; 1 warning emitted

error: test failed, to rerun pass `--lib`

Caused by:
  process didn't exit successfully: `/home/lucas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner /mnt/c/projects/temp/rs-matter-stack/target/miri/x86_64-unknown-linux-gnu/debug/deps/rs_matter_stack-42303a557d0b917b` (exit status: 1)
note: test exited abnormally; to see the full output pass --no-capture to the harness.

The fix here is

// Safety: We just allocated the memory and it's properly aligned
let ptr = unsafe {
-     let ptr = t_buf.as_ptr() as *mut T;
+     let ptr = t_buf.as_mut_ptr() as *mut T;
    ptr.write(object);

    NonNull::new_unchecked(ptr)
};

and with that fix, miri will show the same problem on master that my PR has:

     Running unittests src/lib.rs (target/miri/x86_64-unknown-linux-gnu/debug/deps/rs_matter_stack-42303a557d0b917b)
warning: Miri does not support optimizations: the opt-level is ignored. The only effect of selecting a Cargo profile that enables optimizations (such as --release) is to apply its remaining settings, such as whether debug assertions and overflow checks are enabled.


running 2 tests
test bump::tests::test_multiple_concurrent_borrow ... error: Undefined Behavior: trying to retag from <134984> for SharedReadOnly permission at alloc233[0x10], but that tag does not exist in the borrow stack for this location
   --> /home/lucas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:444:18
    |
444 |         unsafe { &*self.as_ptr().cast_const() }
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of retag at alloc233[0x10..0x18]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <134984> was created by a SharedReadWrite retag at offsets [0x10..0x18]
   --> src/bump.rs:127:27
    |
127 |                 let ptr = t_buf.as_mut_ptr() as *mut T;
    |                           ^^^^^^^^^^^^^^^^^^
help: <134984> was later invalidated at offsets [0x10..0x418] by a Unique retag
   --> src/bump.rs:133:13
    |
133 |             inner.offset += remaining_len - r_buf.len();
    |             ^^^^^^^^^^^^
    = note: BACKTRACE (of the first span) on thread `bump::tests::te`:
    = note: inside `core::ptr::NonNull::<usize>::as_ref::<'_>` at /home/lucas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:444:18: 444:46
note: inside `<bump::BumpBox<'_, usize> as core::ops::Deref>::deref`
   --> src/bump.rs:163:18
    |
163 |         unsafe { self.ptr.as_ref() }
    |                  ^^^^^^^^^^^^^^^^^
note: inside `bump::tests::test_multiple_concurrent_borrow`
   --> src/bump.rs:262:24
    |
262 |             assert_eq!(*b, i);
    |                        ^^
note: inside closure
   --> src/bump.rs:253:41
    |
252 |     #[test]
    |     ------- in this attribute macro expansion
253 |     fn test_multiple_concurrent_borrow() {
    |                                         ^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error; 1 warning emitted

error: test failed, to rerun pass `--lib`

Caused by:
  process didn't exit successfully: `/home/lucas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner /mnt/c/projects/temp/rs-matter-stack/target/miri/x86_64-unknown-linux-gnu/debug/deps/rs_matter_stack-42303a557d0b917b` (exit status: 1)
note: test exited abnormally; to see the full output pass --no-capture to the harness.

The problem is this:

help: <135022> was created by a SharedReadWrite retag at offsets [0x10..0x18]
   --> src/bump.rs:135:54
    |
135 |                 ptr: unsafe { NonNull::new_unchecked(value.as_mut_ptr()) },
    |                                                      ^^^^^^^^^^^^^^^^^^
help: <135022> was later invalidated at offsets [0x10..0x418] by a Unique retag
   --> src/bump.rs:129:25
    |
129 |             let value = inner.allocate::<T>();
    |                         ^^^^^^^^^^^^^^^^^^^^^

It will return a pointer to the offsets [0x10..0x18] on the first allocation, then on the next call, it borrows the &mut self (-> it borrows the full memory array, which is from [0x10..0x418]).

Given that an &mut is supposed to be exclusive access, miri is not happy about it.

I have tried a few things, but none of them made miri happy.

@ivmarkov
Copy link
Collaborator

I see.

I played a bit with it today, and I think you are right - miri just does not like our align_min function, as it is playing with the "complete" MaybeUninit slice, even though portions of it had been "given out" as BumpBoxes in the meantime.

I'm not sure there is necessarily a bug in it (?) but I'm also not sure how to fix it so that miri stops complaining.
It could even be, that the "fix" might require hiding a lot of the pointer arithmetic behind raw pointers, which might not necessarily make it "more safe" so to say. For one, I don't want to lose the access to the align_to_mut as that's what makes the whole approach so simple (we don't have to do aligned alloc in a memory array ourselves "manually", with pointer arithmetics, which would otherwise be a bit more hairy).

Let me think about it a bit more in the next couple of days (a bit busy with other work ATM).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants