Skip to content

Add API to use stack-allocated buffers safely. #11

@Dirbaio

Description

@Dirbaio

Currently using stack-allocated buffers is impossible without unsafe. Here are two ideas on how to do this, coming from our conversations in Matrix :)

I'm not sure which one is best, we should explore tradeoffs and decide (or have both?)

Method 1: closure-based

pub struct Buffer<'a> {
    inner: &'a mut [u8],
}

unsafe impl<'a> ReadBuffer for Buffer<'a> {
    type Word = u8;
    unsafe fn read_buffer(&self) -> (*const Self::Word, usize) {
        (self.inner.as_ptr(), self.inner.len())
    }
}

unsafe impl<'a> WriteBuffer for Buffer<'a> {
    type Word = u8;
    unsafe fn write_buffer(&mut self) -> (*mut Self::Word, usize) {
        (self.inner.as_mut_ptr(), self.inner.len())
    }
}

fn with_buffer<'a, R>(buf: &'a mut [u8], f: impl FnOnce(Buffer<'a>) -> (R, Buffer<'a>)) -> R {
    let orig_loc = (buf.as_ptr(), buf.len());
    let (r, ret_buf) = f(Buffer { inner: buf });
    let ret_loc = (ret_buf.inner.as_ptr(), ret_buf.inner.len());
    core::assert_eq!(orig_loc, ret_loc); // Not sure if this is needed for safety.
    r
}

Method 2: flag-based

#[feature(min_const_generics)]
use core::marker::PhantomPinned;
use core::panic;
use core::pin::Pin;
//use embedded_dma::{ReadBuffer, WriteBuffer};
use futures::pin_mut;

struct DmaBuffer<B: ?Sized> {
    pinned: PhantomPinned,
    in_use: bool,
    buf: B,
}

impl<B> DmaBuffer<B> {
    pub fn new(b: B) -> Self {
        Self {
            pinned: PhantomPinned,
            in_use: false,
            buf: b,
        }
    }
}

impl<const N: usize> DmaBuffer<[u8; N]> {
    pub fn borrow<'a>(self: Pin<&'a mut Self>) -> DmaBufferBorrow<'a, [u8]> {
        let this = unsafe { self.get_unchecked_mut() };
        if this.in_use {
            panic!("You can't borrow a buffer two times!")
        }
        this.in_use = true;
        DmaBufferBorrow(unsafe { Pin::new_unchecked(this) })
    }
}

impl<B: ?Sized> Drop for DmaBuffer<B> {
    fn drop(&mut self) {
        if self.in_use {
            panic!("You leaked a buffer borrow!")
        }
    }
}

struct DmaBufferBorrow<'a, B: ?Sized>(Pin<&'a mut DmaBuffer<B>>);

impl<'a, B: ?Sized> Drop for DmaBufferBorrow<'a, B> {
    fn drop(&mut self) {
        unsafe { self.0.as_mut().get_unchecked_mut() }.in_use = false;
    }
}

fn use_buffer<'a>(buf: DmaBufferBorrow<'a, [u8]>) {}

unsafe impl<'a> ReadBuffer for DmaBufferBorrow<'a, [u8]> {
    type Word = u8;
    unsafe fn read_buffer(&self) -> (*const Self::Word, usize) {
        (self.0.buf.as_ptr(), self.0.buf.len())
    }
}

unsafe impl<'a> WriteBuffer for DmaBufferBorrow<'a, [u8]> {
    type Word = u8;
    unsafe fn write_buffer(&mut self) -> (*mut Self::Word, usize) {
        let buf = unsafe { self.0.as_mut().get_unchecked_mut() };
        (buf.buf.as_mut_ptr(), buf.buf.len())
    }
}

fn main() {
    let mut buf = DmaBuffer::new([0u8; 1024]);
    pin_mut!(buf);

    println!("in use: {}", buf.in_use);
    let mut borrow = buf.as_mut().borrow();
    //println!("in use: {}", buf.in_use); This would print true
    use_buffer(borrow);
    println!("in use: {}", buf.in_use);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions