Skip to content

Commit 048cf61

Browse files
authored
Merge pull request #43 from ausimian/phase-3-async-to-binary
Phase 3: convert to_binary to async enif_send
2 parents 75ebeed + 83cfc57 commit 048cf61

2 files changed

Lines changed: 36 additions & 21 deletions

File tree

c_src/emily_nif.cpp

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,29 +69,40 @@ fine::ResourcePtr<Tensor> from_binary(
6969
}
7070
FINE_NIF(from_binary, 0);
7171

72-
// to_binary/2 — materialize the array and return its bytes as a BEAM
73-
// resource binary aliasing MLX storage (no memcpy). The binary pins a
74-
// Tensor resource → mx::array → MLX buffer; the buffer survives until
75-
// the BEAM binary is GC'd.
76-
fine::Term to_binary(ErlNifEnv *env, fine::ResourcePtr<WorkerThread> w, fine::ResourcePtr<Tensor> tensor) {
77-
auto materialized = w->run_sync([&](mx::Stream &s) {
78-
auto c = mx::contiguous(tensor->array, false, s);
79-
mx::eval(c);
80-
return c;
81-
});
82-
83-
if (!materialized.flags().row_contiguous) {
84-
throw std::runtime_error(
85-
"to_binary: array is not row-contiguous after mx::contiguous");
86-
}
72+
// to_binary_nif/2 — materialize the array on the worker thread and
73+
// return its bytes as a BEAM resource binary aliasing MLX storage
74+
// (no memcpy). Async: the NIF returns a ref immediately; the worker
75+
// posts `{ref, {:ok, bin}}` back to the caller once `mx::contiguous +
76+
// mx::eval` completes. The Elixir wrapper `Emily.Native.to_binary/2`
77+
// awaits via `Emily.Native.Async.call/1`.
78+
//
79+
// Pinning: `fine::make_resource_binary` called on the worker-allocated
80+
// msg_env bumps the Tensor resource's refcount; the binary carries
81+
// that ref into the receiving process, where it stays alive until
82+
// the binary is GC'd. Validated by Spike B.
83+
fine::Term to_binary_nif(ErlNifEnv *env,
84+
fine::ResourcePtr<WorkerThread> w,
85+
fine::ResourcePtr<Tensor> tensor) {
86+
return emily::async_reply(
87+
env, w,
88+
[tensor = std::move(tensor)](mx::Stream &s, ErlNifEnv *msg_env) {
89+
auto materialized = mx::contiguous(tensor->array, false, s);
90+
mx::eval(materialized);
91+
92+
if (!materialized.flags().row_contiguous) {
93+
throw std::runtime_error(
94+
"to_binary: array is not row-contiguous after mx::contiguous");
95+
}
8796

88-
auto nbytes = materialized.nbytes();
89-
auto data = reinterpret_cast<const char *>(materialized.data<void>());
97+
auto nbytes = materialized.nbytes();
98+
auto data = reinterpret_cast<const char *>(materialized.data<void>());
9099

91-
auto pin = wrap(std::move(materialized));
92-
return fine::make_resource_binary(env, std::move(pin), data, nbytes);
100+
auto pin = wrap(std::move(materialized));
101+
return fine::Term(
102+
fine::make_resource_binary(msg_env, std::move(pin), data, nbytes));
103+
});
93104
}
94-
FINE_NIF(to_binary, ERL_NIF_DIRTY_JOB_CPU_BOUND);
105+
FINE_NIF(to_binary_nif, 0);
95106

96107
// shape/1 — return the array's shape as a list of ints.
97108
std::vector<int64_t> shape(ErlNifEnv *, fine::ResourcePtr<Tensor> tensor) {

lib/emily/native.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ defmodule Emily.Native do
4040
@spec from_binary(binary(), [non_neg_integer()], dtype()) :: tensor()
4141
def from_binary(_data, _shape, _dtype), do: nif()
4242

43+
@doc false
44+
@spec to_binary_nif(worker(), tensor()) :: reference()
45+
def to_binary_nif(_w, _tensor), do: nif()
46+
4347
@spec to_binary(worker(), tensor()) :: binary()
44-
def to_binary(_w, _tensor), do: nif()
48+
def to_binary(w, tensor), do: Async.call(to_binary_nif(w, tensor))
4549

4650
@spec shape(tensor()) :: [non_neg_integer()]
4751
def shape(_tensor), do: nif()

0 commit comments

Comments
 (0)