Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Payload data with aligned unique_ptr

I have unique_ptr<T> which is always aligned to 4096 - means the lowest 12bits of the pointer value are always equal to 0. My intension is to store a byte of payload data / metadata "in within the pointer itself". I mean to create a class which will have a move converting constructor like:

template<typename T>
struct PayloadPtr {
    /* ... */
    PayloadPTr( unique_ptr<T> ptr ) noexcept { ... }
    /* ... */
};

which will 'sink' the unique_ptr<T> and let it store as member but reusing the lowest 12bits to store the byte variable... The overall layout is important as given by the HW.

{
    uint32_t payload : 12;
    uint32_t pointer : 20;
}

The whole 32bit value can be read as pointer masking out the payload, the payload can be read out masking the highest 20bits... Important stuff is the T's constructor/destructor is not called during the storage within the PayloadPtr, just like regular unique_ptr member variable storage.

How to achieve such behavior? To use union having unique_ptr as one member while having the payload as other? Or to store is as plain data (e.g. uintptr_t) and reinterpret the data in getters/setters?

What would be the recommended way to implement that? Thanks to anyone willing to help!

like image 943
Martin Kopecký Avatar asked Apr 18 '26 04:04

Martin Kopecký


1 Answers

You could create something similar to unique_ptr but storing the pointer as a std::uintptr_t and cast and filter out the payload when needed.

Here's an outline what it could look like:

#ifndef __cpp_aligned_new
#error "Requires Dynamic memory allocation for over-aligned data (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r4.html)"
#else

#include <cstdint>
#include <utility>

template<typename T>
class PayloadPtr {
public:
    static_assert(alignof(T) % 0x1000 == 0, "must be aligned to a multiple of 0x1000");

    // construct from a T*
    PayloadPtr(T* t) : data(reinterpret_cast<std::uintptr_t>(t)) {}

    // take over ownershipt from a unique_ptr
    PayloadPtr(std::unique_ptr<T>&& o) : PayloadPtr(o.release()) {}

    // rule of 5
    PayloadPtr(const PayloadPtr&) = delete;
    PayloadPtr(PayloadPtr&& o) noexcept : data(std::exchange(o.data, 0)) {}
    PayloadPtr& operator=(const PayloadPtr&) = delete;
    PayloadPtr& operator=(PayloadPtr&& o) noexcept {
        std::swap(data, o.data);
        return *this;
    }
    ~PayloadPtr() { delete get(); }

    T* get() const { return reinterpret_cast<T*>(data & ~0xFFFULL); }
    T* release() { T* rv = get(); data = 0; return rv; }

    // dereferencing 
    T& operator*() const { return *get(); }
    T* operator->() const { return get(); }

    explicit operator bool() const noexcept { return get() != nullptr; }

    void reset(T* o = nullptr) noexcept {
        delete get();
        data = reinterpret_cast<std::uintptr_t>(o);
    }

    void set_payload(std::uint16_t p) { data = (data & ~0xFFFULL) | (p & 0xFFF); }
    std::uint16_t get_payload() const { return data & 0xFFF; }

private:
    std::uintptr_t data;
};

#endif

Demo

like image 81
Ted Lyngmo Avatar answered Apr 20 '26 20:04

Ted Lyngmo