04 Dec

Baby’s First Crate: cpp_new

So I’ve been playing around with Rust in my spare time for a few months now, and have been doing a lot of stuff that bridges C/C++ and Rust content. And one thing that has always bugged me is that I can use a super awesome allocator like jemalloc in Rust, but that the allocations being done via C++ were not being routed into this allocator. So as a tiny baby step into the Rust community, I’ve released my first crate cpp_new. This crate simply maps C++’s new/delete/new[]/delete[] via extern “C” functions back into Rust’s global allocators.

To use, you simply do:

extern crate cpp_new;

And Rust will take care of the rest. This allows you to map any C++ allocations in existing content into a single allocator across your C++ and Rust content!

How It Is Implemented

To implement the crate there are two source files, the first being lib.rs which contains two functions rust_cpp_new and rust_cpp_delete. Rust’s global allocators use a Layout to allocate memory. A layout is effectively a struct with two usize’s in it – one is the size of the memory and the other is the alignment. This is fine for rust_cpp_new, because we can pass this information from the C++ side. But it becomes slightly trickier for rust_cpp_delete, because C++ only provides us with a void* of the ‘memory to free’, but Rust expects an identical layout that was used to create the memory to delete it.

To get around this, we don’t just allocate the memory as was requested by the user, we also fudge on a little blob of memory just before the allocation we return to the user that contains the Layout.

#[no_mangle]
pub unsafe extern "C" fn rust_cpp_new(
    size: usize,
    alignment: usize,
) -> *mut c_void {
    let offset = max(LAYOUT_ALIGNMENT, LAYOUT_SIZE);
    let new_alignment = max(offset, alignment);
    let new_size = size + new_alignment;

    if new_alignment >= (isize::MAX as usize) {
        null_mut()
    } else {
        match Layout::from_size_align(new_size, new_alignment) {
            Ok(layout) => {
                let allocation = alloc(layout);
                let result_allocation =
                    allocation.offset(new_alignment as isize);
                let hidden_allocation =
                    result_allocation.offset(-(offset as isize)) as *mut Layout;
                hidden_allocation.write(layout);

                result_allocation as *mut c_void
            }
            Err(_) => null_mut(),
        }
    }
}

While this looks a little confusing – all we are doing is making room for the Layout and storing it. Now when we are freeing memory we need to be careful to undo the fudge and get to the start of the Layout instead of the start of the user provided pointer:

#[no_mangle]
pub unsafe extern "C" fn rust_cpp_delete(payload: *const c_void) {
    let offset = max(LAYOUT_ALIGNMENT, LAYOUT_SIZE);

    if !payload.is_null() {
        let result_allocation = payload as *mut u8;
        let hidden_allocation =
            result_allocation.offset(-(offset as isize)) as *const Layout;
        let layout = hidden_allocation.read();
        let allocation = result_allocation.offset(-(layout.align() as isize));
        dealloc(allocation, layout);
    }
}

And that’s it! Now the second file is new.cpp – which contains the definitions of C++’s new/delete/new[]/delete[].

For both the single-object and array we use the same Rust methods. We also have no information on the alignment requirements so we need to choose some value. I’ve chosen 16 bytes alignment because that is the alignment that stackoverflow told me some C++ implementations use. It at least covers all the base use cases, and since Layout is two usize’s (and thus 16 bytes itself) it means we’ve got no wasted bytes in the allocation.

Conclusion

This crate is super simple, super easy to use, and was a nice introduction to how to package up a Rust crate for use. The crate is provided under the CC0 1.0 Universal public domain and permissible license, and I hope it proves useful to someone!

Leave a Reply

Your email address will not be published. Required fields are marked *