@@ -69,29 +69,40 @@ fine::ResourcePtr<Tensor> from_binary(
6969}
7070FINE_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.
97108std::vector<int64_t > shape (ErlNifEnv *, fine::ResourcePtr<Tensor> tensor) {
0 commit comments