Module Spoc_core.Memory

Pure element-size helper

val bigarray_elem_size : 'a 'b. ('a, 'b) Stdlib.Bigarray.kind -> int

Byte size of each Bigarray element kind on a 64-bit target. Replaces Ctypes_static.sizeof (Ctypes.typ_of_bigarray_kind kind) so that the numeric-only path is free of ctypes. Sizes are identical to what ctypes sizeof returns on 64-bit: the underlying C representation widths.

Host bigarray -> raw pointer

val bigarray_void_ptr : 'a 'b. ('a, 'b, Stdlib.Bigarray.c_layout) Stdlib.Bigarray.Array1.t -> unit Ctypes.ptr

Raw data pointer of a host bigarray, for the H2D/D2H byte transfers.

Ctypes.bigarray_start cannot be used for f16: ctypes maps a Bigarray.kind to its own Ctypes_bigarray.kind GADT, and that mapping has NO Float16 arm — it ends in failwith "Unsupported bigarray kind" (ctypes_bigarray_stubs.ml). An f16 vector therefore raised on its first transfer even though every byte size involved was already correct.

This corrects a claim in the f16 design spec (§3.3, "transfer is element-type-agnostic beyond byte size — no change needed"): the transfer ARITHMETIC is indeed element-agnostic, but ACQUIRING the host pointer is not.

Ctypes_bigarray.unsafe_address is the kind-independent primitive (it is just Caml_ba_data_val) and is used for f16 only, keeping every other element type on the exact pre-existing code path.

GC ROOTS (#57 slice 1 review, MF3). The f16 arm must NOT be built with Ctypes.ptr_of_raw_address: that is make_unmanaged, and it silently drops the GC root that Ctypes.bigarray_start establishes. ctypes' own bigarray_start returns a MANAGED fat pointer — Fat.make ~managed:(Some (Obj.repr ba)) ~reftyp raw (ctypes_memory.ml :302-305) — so for every non-f16 kind the pointer VALUE is itself a root for the bigarray, and ctypes' FFI keeps it alive across the call (ctypes_ffi.ml:112-119). Probe, three pointers built over the same shape and then subjected to a major GC with the pointer still live:

f32 bigarray_start (managed)           : freed during ffi call = 0
f16 ptr_of_raw_address (unmanaged)     : freed during ffi call = 1
f16 Fat.make ~managed (this arm)       : freed during ffi call = 0

So the f16 arm reconstructs the SAME fat-pointer shape as ctypes, over the kind-independent address. That restores pre-existing GC semantics for f16 with zero per-caller discipline, rather than asking five call sites to remember a keepalive (three of which did not).

This does deepen an existing dependency on ctypes internals — already present via Ctypes_bigarray.unsafe_address here and via Ctypes_ptr.voidp = nativeint at Vector_transfer/Framework_sig — now also on Ctypes_static.CPointer and Ctypes_ptr.Fat.make. Both are exposed in ctypes_static.mli (the pointer GADT) and ctypes_ptr.ml's signature, and they are the only way to express "managed pointer to a Float16 bigarray" while ctypes' kind GADT has no Float16 arm. If ctypes ever grows one, this whole function collapses back to bigarray_start.

Callers converting the result to a bare nativeint via Ctypes.raw_address_of_ptr strip the root again — that is a SEPARATE obligation, stated verbatim at Framework_sig.ml:218-223, and it applies to every element type, not just f16. It is discharged with Sys.opaque_identity at the sites below and in Transfer.ml.

Buffer Module Type

module type BUFFER = sig ... end

A buffer packages backend-specific buffer with its operations. All transfers use raw pointers with byte sizes to avoid type parameter escaping issues in first-class modules.

type _ buffer =
  1. | Buffer : (module BUFFER) -> 'a buffer

Buffer with phantom type parameter for element type safety. The 'a parameter is not used at runtime but ensures type-safe transfers.

Allocation

val alloc : Device.t -> int -> ('a, 'b) Stdlib.Bigarray.kind -> 'a buffer

Allocate a buffer on a device for standard Bigarray types

val alloc_custom : Device.t -> size:int -> elem_size:int -> 'a buffer

Allocate a buffer for custom types with explicit element size in bytes

Buffer Operations

val free : 'a. 'a buffer -> unit

Free a buffer

val host_to_device : 'a 'b. src:('a, 'b, Stdlib.Bigarray.c_layout) Stdlib.Bigarray.Array1.t -> dst:'a buffer -> unit

Copy data from host bigarray to device. Converts bigarray to pointer internally. Type parameter ensures bigarray element type matches buffer type.

val device_to_host : 'a 'b. src:'a buffer -> dst:('a, 'b, Stdlib.Bigarray.c_layout) Stdlib.Bigarray.Array1.t -> unit

Copy data from device to host bigarray. Converts bigarray to pointer internally. Type parameter ensures bigarray element type matches buffer type.

val host_ptr_to_device : 'a. src_ptr:unit Ctypes.ptr -> dst:'a buffer -> unit

Copy data from raw pointer to device buffer (for custom types)

val device_to_host_ptr : 'a. src:'a buffer -> dst_ptr:unit Ctypes.ptr -> unit

Copy data from device buffer to raw pointer (for custom types)

val device_to_device : 'a. src:'a buffer -> dst:'a buffer -> unit

Copy data between device buffers (same device). Type parameter ensures both buffers have same element type.

Accessors

val size : 'a. 'a buffer -> int

Get buffer size in elements

val elem_size : 'a. 'a buffer -> int

Get buffer element size in bytes

val device : 'a. 'a buffer -> Device.t

Get buffer device

val device_ptr : 'a. 'a buffer -> nativeint

Get raw device pointer

val bind_to_kargs : 'a. 'a buffer -> Spoc_framework.Framework_sig.kargs -> int -> unit

Bind buffer to kernel args