#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <atomic>
#include <cstdint>
#include <limits>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
namespace {
constexpr const char* kArenaCapsuleName = "modelexpress.VmmArenaState";
constexpr int kStateOpen = 0;
constexpr int kStateClosed = 1;
struct AllocRecord {
size_t size;
uint64_t handle;
};
struct ArenaState {
uintptr_t base;
size_t total_bytes;
size_t granularity;
std::atomic<size_t> next_offset;
std::atomic<size_t> mapped_bytes;
std::atomic<int> state;
std::atomic<int> in_flight;
PyObject* alloc_callback;
PyObject* dealloc_callback;
std::mutex allocations_mutex;
std::unordered_map<uintptr_t, AllocRecord> allocations;
};
class InFlightGuard {
public:
explicit InFlightGuard(ArenaState* s) : state_(s) {
state_->in_flight.fetch_add(1, std::memory_order_seq_cst);
}
~InFlightGuard() {
state_->in_flight.fetch_sub(1, std::memory_order_seq_cst);
}
InFlightGuard(const InFlightGuard&) = delete;
InFlightGuard& operator=(const InFlightGuard&) = delete;
private:
ArenaState* state_;
};
std::mutex g_active_mutex;
PyObject* g_active_capsule = nullptr;
ArenaState*
get_state(PyObject* capsule)
{
return static_cast<ArenaState*>(PyCapsule_GetPointer(capsule, kArenaCapsuleName));
}
bool
is_power_of_two(size_t value)
{
return value != 0 && (value & (value - 1)) == 0;
}
bool
round_up(size_t value, size_t granularity, size_t* out)
{
if (granularity == 0) {
return false;
}
size_t remainder = value % granularity;
if (remainder == 0) {
*out = value;
return true;
}
size_t add = granularity - remainder;
if (value > std::numeric_limits<size_t>::max() - add) {
return false;
}
*out = value + add;
return true;
}
void
arena_capsule_destructor(PyObject* capsule)
{
ArenaState* state = get_state(capsule);
if (!state) {
PyErr_Clear();
return;
}
PyGILState_STATE gstate = PyGILState_Ensure();
Py_XDECREF(state->alloc_callback);
Py_XDECREF(state->dealloc_callback);
PyGILState_Release(gstate);
delete state;
}
PyObject*
call_alloc_callback(ArenaState* state, uintptr_t va, size_t size)
{
PyObject* cb = state->alloc_callback;
Py_INCREF(cb);
PyObject* result = PyObject_CallFunction(
cb, "KK", static_cast<unsigned long long>(va),
static_cast<unsigned long long>(size));
Py_DECREF(cb);
return result;
}
bool
call_dealloc_callback(
ArenaState* state, uintptr_t va, size_t size, uint64_t handle,
bool write_unraisable)
{
PyObject* cb = state->dealloc_callback;
Py_INCREF(cb);
PyObject* result = PyObject_CallFunction(
cb, "KKK", static_cast<unsigned long long>(va),
static_cast<unsigned long long>(size),
static_cast<unsigned long long>(handle));
if (!result) {
if (write_unraisable) {
PyErr_WriteUnraisable(cb);
} else {
PyErr_Clear();
}
Py_DECREF(cb);
return false;
}
Py_DECREF(result);
Py_DECREF(cb);
return true;
}
PyObject*
arena_malloc_impl(ArenaState* state, Py_ssize_t requested_size)
{
if (requested_size <= 0) {
PyErr_SetString(PyExc_ValueError, "size must be positive");
return nullptr;
}
InFlightGuard guard(state);
if (state->state.load(std::memory_order_seq_cst) != kStateOpen) {
PyErr_SetString(PyExc_RuntimeError, "arena is closed");
return nullptr;
}
size_t aligned_size = 0;
if (!round_up(static_cast<size_t>(requested_size), state->granularity, &aligned_size)) {
PyErr_SetString(PyExc_OverflowError, "allocation size overflow during alignment");
return nullptr;
}
size_t offset = state->next_offset.load(std::memory_order_relaxed);
bool overflow = false;
Py_BEGIN_ALLOW_THREADS
while (true) {
if (offset > state->total_bytes || aligned_size > state->total_bytes - offset) {
overflow = true;
break;
}
size_t next = offset + aligned_size;
if (state->next_offset.compare_exchange_weak(
offset, next, std::memory_order_acq_rel, std::memory_order_relaxed)) {
break;
}
}
Py_END_ALLOW_THREADS
if (overflow) {
PyErr_Format(
PyExc_RuntimeError,
"allocation of %zd bytes (aligned %zu) at offset %zu would exceed reserved range of %zu bytes",
requested_size, aligned_size, offset, state->total_bytes);
return nullptr;
}
uintptr_t va = state->base + offset;
PyObject* result = call_alloc_callback(state, va, aligned_size);
if (!result) {
return nullptr;
}
unsigned long long handle_value = PyLong_AsUnsignedLongLong(result);
Py_DECREF(result);
if (PyErr_Occurred()) {
return nullptr;
}
Py_BEGIN_ALLOW_THREADS
{
std::lock_guard<std::mutex> lock(state->allocations_mutex);
state->allocations.emplace(
va, AllocRecord{aligned_size, static_cast<uint64_t>(handle_value)});
}
state->mapped_bytes.fetch_add(aligned_size, std::memory_order_acq_rel);
Py_END_ALLOW_THREADS
return PyLong_FromUnsignedLongLong(static_cast<unsigned long long>(va));
}
void
arena_free_impl(ArenaState* state, uintptr_t ptr, bool write_unraisable)
{
InFlightGuard guard(state);
AllocRecord record{};
bool found = false;
Py_BEGIN_ALLOW_THREADS
if (state->state.load(std::memory_order_seq_cst) == kStateOpen) {
{
std::lock_guard<std::mutex> lock(state->allocations_mutex);
auto it = state->allocations.find(ptr);
if (it != state->allocations.end()) {
record = it->second;
state->allocations.erase(it);
found = true;
}
}
if (found) {
state->mapped_bytes.fetch_sub(record.size, std::memory_order_acq_rel);
}
}
Py_END_ALLOW_THREADS
if (!found) {
return;
}
call_dealloc_callback(
state, ptr, record.size, record.handle, write_unraisable);
}
}
extern "C" {
void*
mx_malloc(ssize_t size, int device, void* stream)
{
(void)device;
(void)stream;
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject* capsule = nullptr;
{
std::lock_guard<std::mutex> lock(g_active_mutex);
capsule = g_active_capsule;
Py_XINCREF(capsule);
}
if (!capsule) {
PyGILState_Release(gstate);
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
PyErr_WriteUnraisable(capsule);
Py_DECREF(capsule);
PyGILState_Release(gstate);
return nullptr;
}
void* ptr = nullptr;
PyObject* result = arena_malloc_impl(state, static_cast<Py_ssize_t>(size));
if (result) {
unsigned long long val = PyLong_AsUnsignedLongLong(result);
if (!PyErr_Occurred()) {
ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(val));
}
Py_DECREF(result);
}
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(capsule);
}
Py_DECREF(capsule);
PyGILState_Release(gstate);
return ptr;
}
void
mx_free(void* ptr, ssize_t size, int device, void* stream)
{
(void)size;
(void)device;
(void)stream;
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject* capsule = nullptr;
{
std::lock_guard<std::mutex> lock(g_active_mutex);
capsule = g_active_capsule;
Py_XINCREF(capsule);
}
if (capsule) {
ArenaState* state = get_state(capsule);
if (state) {
arena_free_impl(state, reinterpret_cast<uintptr_t>(ptr), true);
} else {
PyErr_WriteUnraisable(capsule);
}
Py_DECREF(capsule);
}
PyGILState_Release(gstate);
}
}
static PyObject*
py_arena_create(PyObject* self, PyObject* args)
{
(void)self;
unsigned long long base = 0;
unsigned long long total_bytes = 0;
unsigned long long granularity = 0;
PyObject* alloc_cb = nullptr;
PyObject* dealloc_cb = nullptr;
if (!PyArg_ParseTuple(
args, "KKKOO", &base, &total_bytes, &granularity, &alloc_cb,
&dealloc_cb)) {
return nullptr;
}
if (!PyCallable_Check(alloc_cb) || !PyCallable_Check(dealloc_cb)) {
PyErr_SetString(PyExc_TypeError, "allocation callbacks must be callable");
return nullptr;
}
if (!is_power_of_two(static_cast<size_t>(granularity))) {
PyErr_SetString(PyExc_ValueError, "granularity must be a positive power of 2");
return nullptr;
}
auto* state = new ArenaState{
static_cast<uintptr_t>(base),
static_cast<size_t>(total_bytes),
static_cast<size_t>(granularity),
0,
0,
kStateOpen,
0, alloc_cb,
dealloc_cb,
{},
{}};
Py_INCREF(alloc_cb);
Py_INCREF(dealloc_cb);
PyObject* capsule = PyCapsule_New(state, kArenaCapsuleName, arena_capsule_destructor);
if (!capsule) {
Py_DECREF(alloc_cb);
Py_DECREF(dealloc_cb);
delete state;
return nullptr;
}
return capsule;
}
static PyObject*
py_arena_malloc(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
Py_ssize_t size = 0;
if (!PyArg_ParseTuple(args, "On", &capsule, &size)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
return arena_malloc_impl(state, size);
}
static PyObject*
py_arena_free(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
unsigned long long ptr = 0;
if (!PyArg_ParseTuple(args, "OK", &capsule, &ptr)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
arena_free_impl(state, static_cast<uintptr_t>(ptr), true);
Py_RETURN_NONE;
}
static PyObject*
py_arena_registered_range(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
return Py_BuildValue(
"KK", static_cast<unsigned long long>(state->base),
static_cast<unsigned long long>(
state->next_offset.load(std::memory_order_acquire)));
}
static PyObject*
py_arena_get_used_bytes(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
return PyLong_FromUnsignedLongLong(
static_cast<unsigned long long>(state->next_offset.load(std::memory_order_acquire)));
}
static PyObject*
py_arena_get_mapped_bytes(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
return PyLong_FromUnsignedLongLong(
static_cast<unsigned long long>(state->mapped_bytes.load(std::memory_order_acquire)));
}
static PyObject*
py_arena_get_live_count(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
size_t live_count = 0;
Py_BEGIN_ALLOW_THREADS
{
std::lock_guard<std::mutex> lock(state->allocations_mutex);
live_count = state->allocations.size();
}
Py_END_ALLOW_THREADS
return PyLong_FromSize_t(live_count);
}
static PyObject*
py_arena_is_closed(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
if (state->state.load(std::memory_order_acquire) == kStateClosed) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static PyObject*
py_arena_close_and_drain(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
int expected = kStateOpen;
if (!state->state.compare_exchange_strong(
expected, kStateClosed, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
Py_RETURN_NONE;
}
Py_BEGIN_ALLOW_THREADS
while (state->in_flight.load(std::memory_order_seq_cst) > 0) {
std::this_thread::yield();
}
Py_END_ALLOW_THREADS
std::vector<std::pair<uintptr_t, AllocRecord>> drained;
{
std::lock_guard<std::mutex> lock(state->allocations_mutex);
drained.reserve(state->allocations.size());
for (const auto& item : state->allocations) {
drained.emplace_back(item.first, item.second);
}
}
PyObject* list = PyList_New(static_cast<Py_ssize_t>(drained.size()));
if (!list) {
state->state.store(kStateOpen, std::memory_order_seq_cst);
return nullptr;
}
for (Py_ssize_t i = 0; i < static_cast<Py_ssize_t>(drained.size()); ++i) {
PyObject* tuple = Py_BuildValue(
"KKK", static_cast<unsigned long long>(drained[i].first),
static_cast<unsigned long long>(drained[i].second.size),
static_cast<unsigned long long>(drained[i].second.handle));
if (!tuple) {
Py_DECREF(list);
state->state.store(kStateOpen, std::memory_order_seq_cst);
return nullptr;
}
PyList_SET_ITEM(list, i, tuple);
}
{
std::lock_guard<std::mutex> lock(state->allocations_mutex);
state->allocations.clear();
}
state->mapped_bytes.store(0, std::memory_order_release);
return list;
}
static PyObject*
py_set_active_arena(PyObject* self, PyObject* args)
{
(void)self;
PyObject* capsule = nullptr;
if (!PyArg_ParseTuple(args, "O", &capsule)) {
return nullptr;
}
PyObject* new_capsule = nullptr;
if (capsule != Py_None) {
ArenaState* state = get_state(capsule);
if (!state) {
return nullptr;
}
new_capsule = capsule;
Py_INCREF(new_capsule);
}
PyObject* old_capsule = nullptr;
{
std::lock_guard<std::mutex> lock(g_active_mutex);
old_capsule = g_active_capsule;
g_active_capsule = new_capsule;
}
Py_XDECREF(old_capsule);
Py_RETURN_NONE;
}
static PyObject*
py_init_module(PyObject* self, PyObject* args)
{
(void)self;
PyObject* malloc_cb = nullptr;
PyObject* free_cb = nullptr;
if (!PyArg_ParseTuple(args, "OO", &malloc_cb, &free_cb)) {
return nullptr;
}
if (!PyCallable_Check(malloc_cb) || !PyCallable_Check(free_cb)) {
PyErr_SetString(PyExc_TypeError, "Both arguments must be callables");
return nullptr;
}
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"arena_create", py_arena_create, METH_VARARGS, "Create a VmmArena state capsule"},
{"arena_malloc", py_arena_malloc, METH_VARARGS, "Allocate from an arena capsule"},
{"arena_free", py_arena_free, METH_VARARGS, "Free an arena capsule allocation"},
{"arena_registered_range", py_arena_registered_range, METH_VARARGS, "Return (base, used_bytes)"},
{"arena_get_used_bytes", py_arena_get_used_bytes, METH_VARARGS, "Return arena bump pointer"},
{"arena_get_mapped_bytes", py_arena_get_mapped_bytes, METH_VARARGS, "Return live mapped bytes"},
{"arena_get_live_count", py_arena_get_live_count, METH_VARARGS, "Return live allocation count"},
{"arena_is_closed", py_arena_is_closed, METH_VARARGS, "Return whether arena is closed"},
{"arena_close_and_drain", py_arena_close_and_drain, METH_VARARGS, "Close and drain live allocations"},
{"set_active_arena", py_set_active_arena, METH_VARARGS, "Set active arena capsule for mx_malloc/mx_free"},
{"init_module", py_init_module, METH_VARARGS, "Compatibility callback validator"},
{nullptr, nullptr, 0, nullptr}};
#if PY_VERSION_HEX >= 0x030D0000 && defined(Py_mod_gil)
static PyModuleDef_Slot module_slots[] = {
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0, nullptr},
};
#endif
static struct PyModuleDef vmm_alloc_module = {
PyModuleDef_HEAD_INIT,
"_alloc_ext",
"CUDAPluggableAllocator shim for the VmmArena",
0,
module_methods,
#if PY_VERSION_HEX >= 0x030D0000 && defined(Py_mod_gil)
module_slots,
#else
nullptr,
#endif
nullptr,
nullptr,
nullptr};
PyMODINIT_FUNC
PyInit__alloc_ext(void)
{
#if PY_VERSION_HEX >= 0x030D0000 && defined(Py_mod_gil)
return PyModuleDef_Init(&vmm_alloc_module);
#else
return PyModule_Create(&vmm_alloc_module);
#endif
}