Current section

Files

Jump to
emlx c_src emlx_nif_shared.hpp
Raw

c_src/emlx_nif_shared.hpp

#pragma once
// Shared infrastructure used by both emlx_nif.cpp and emlx_fast.cpp.
#include "emlx_async.hpp"
#include "emlx_worker.hpp"
#include "erl_nif.h"
#include "mlx/mlx.h"
#include "nx_nif_utils.hpp"
#include <atomic>
#include <cmath>
#include <cstring>
#include <limits>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
using namespace mlx::core;
using namespace mlx::core::fast;
// MLX 0.31+ uses Shape = SmallVector<int> and Strides = SmallVector<long long>
// which no longer accept implicit construction from std::vector.
static inline mlx::core::Shape to_shape(const std::vector<int> &v) {
return mlx::core::Shape(v.begin(), v.end());
}
static inline mlx::core::Strides to_strides(const std::vector<int64_t> &v) {
return mlx::core::Strides(v.begin(), v.end());
}
inline mlx::core::Device string2device(const std::string &atom) {
if (atom == "cpu") {
return mlx::core::Device(mlx::core::Device::DeviceType::cpu, 0);
} else if (atom == "gpu") {
return mlx::core::Device(mlx::core::Device::DeviceType::gpu, 0);
}
throw std::runtime_error("Unknown device: " + atom);
}
// Class to manage the refcount of MLX tensors
class TensorP {
public:
TensorP(ErlNifEnv *env, const ERL_NIF_TERM arg) : ptr(nullptr) {
// setup
if (!enif_get_resource(env, arg, resource_object<mlx::core::array>::type,
(void **)&ptr)) {
err = nx::nif::error(env, "Unable to get tensor param in NIF");
return;
}
refcount = (std::atomic<int> *)(ptr + 1);
deleted = (std::atomic_flag *)(refcount + 1);
if (refcount->load() == 0) {
// already deallocated
ptr = nullptr;
err = nx::nif::error(env, "Tensor has been deallocated");
return;
}
if (is_valid()) {
// increase reference count
++(*refcount);
}
}
~TensorP() {
if (is_valid()) {
// decrease reference count
if (refcount->fetch_sub(1) == 0) {
ptr->~array(); // Call MLX tensor destructor
}
}
}
bool deallocate() {
if (is_valid() && atomic_flag_test_and_set(deleted) == false) {
--(*refcount);
return true;
} else {
return false;
}
}
mlx::core::array *data() const { return ptr; }
// Raw ERTS resource pointer for use with enif_make_resource_binary.
void *resource_ptr() const { return static_cast<void *>(ptr); }
bool is_valid() const { return ptr != nullptr; }
ERL_NIF_TERM error() { return err; }
private:
mlx::core::array *ptr;
std::atomic<int> *refcount;
std::atomic_flag *deleted;
ERL_NIF_TERM err;
};
#define CATCH() \
catch (const std::exception &e) { \
std::ostringstream msg; \
msg << e.what() << " in NIF." << __func__ << "/" << argc; \
return nx::nif::error(env, msg.str().c_str()); \
} \
catch (...) { \
return nx::nif::error(env, "Unknown error occurred"); \
}
#define TENSOR(A) \
try { \
return nx::nif::ok(env, create_tensor_resource(env, A)); \
} \
CATCH()
#define NIF(NAME) \
ERL_NIF_TERM NAME(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
// One-line async wrapper: declare `NIF(OP) { ... }` then `ASYNC_NIF(OP)`.
// Register in `nif_funcs[]` at `original_arity + 1` (command queue is argv[0]).
// Example: {"add", 4, add_async} // was {"add", 3, add}
#define ASYNC_NIF(OP) \
ERL_NIF_TERM OP##_async(ErlNifEnv *env, int argc, \
const ERL_NIF_TERM argv[]) { \
return emlx::async_dispatch<OP>(env, argc, argv); \
}
#define TENSOR_PARAM(ARGN, VAR) \
TensorP VAR##_tp(env, argv[ARGN]); \
mlx::core::array *VAR; \
if (!VAR##_tp.is_valid()) { \
return VAR##_tp.error(); \
} else { \
VAR = VAR##_tp.data(); \
}
// Forward declaration — defined in emlx_nif.cpp, used in emlx_fast.cpp.
ERL_NIF_TERM create_tensor_resource(ErlNifEnv *env, mlx::core::array tensor);