Metal_api.PendingLaunches are asynchronous: Kernel.execute appends a dispatch to its queue's open command buffer and returns. At each launch the open command buffer is committed if the queue's previous one has completed (the GPU would otherwise idle) or once it holds SPOC_METAL_BATCH dispatches; otherwise it stays open until the next launch, wait, event record or synchronisation commits it. This table remembers every open or committed command buffer that nobody has waited for yet, per command queue, with the MTLBuffers it binds, so that every point where the host can observe or modify device memory waits for exactly the work that touches it:
wait_buffers);wait_buffers_on_other_queues). Within one queue Metal orders command buffers itself: buffers are allocated with the default tracked hazard mode, so a command buffer reading what an earlier one on the same queue wrote waits for it on the GPU;wait_all), stream synchronisation for its queue (wait_queue).The two waits run before, not atomically with, the copy or launch they guard: two domains that launch on different queues, or copy and launch, on the same buffers at the same moment must order themselves, as on every backend.
Freeing an MTLBuffer needs no wait: a command buffer retains the buffers it binds until it completes, so a release only drops SPOC's reference.
A command buffer that ends in MTLCommandBufferStatusError raises kernel_launch_failed at the first wait that observes it, naming every kernel the command buffer ran: the command buffer, not the dispatch, is the unit of failure, and that wait may be for an unrelated buffer of the same batch. Failures still pending at exit are waited for and reported on stderr (at_exit).
type entry = {cmd : unit Ctypes.ptr;Retained by the table until removed.
*)names : string list;Kernels it runs (first few), for errors.
*)buffers : nativeint array;MTLBuffer handles its dispatches bind.
*)}A committed command buffer nobody has waited for yet.
type batch = {bcmd : unit Ctypes.ptr;Retained.
*)encoder : unit Ctypes.ptr;Retained.
*)mutable bnames : string list;mutable bbufs : nativeint list;mutable count : int;}The open (not yet committed) command buffer of a queue, with its compute encoder; launches append dispatches to it.
val queues : (nativeint, queue_state) Stdlib.Hashtbl.tMost dispatches one command buffer collects before it is committed even though the GPU is still busy (SPOC_METAL_BATCH, default 32).
val state_locked : nativeint -> queue_stateCommand buffers committed so far (for tests: batching keeps it well below the number of launches when the GPU is the bottleneck).
val commit_locked : queue_state -> unitval open_batch_locked :
unit Ctypes_static.ptr ->
queue_state ->
name:string ->
batchval encode :
unit Ctypes.ptr ->
name:string ->
buffers:unit Ctypes.ptr list ->
(unit Ctypes.ptr -> unit) ->
unitAppend one dispatch to queue q's open command buffer (encode receives the compute encoder). The batch is committed when it holds batch_limit dispatches, or at once when the queue's previous command buffer has completed (the GPU would otherwise sit idle).
Wait for pending work that binds any of the MTLBuffers hs.
Wait for the pending command buffer c (an event's), reporting its failure through the table so it is reported once. No-op when c is no longer pending (someone already waited for it and reported it).
Wait for pending work on queues other than q that binds any of hs.
Commit queue q's open batch, then return its newest command buffer, retained for the caller (who releases it), or None when the queue has no pending work.
Forget queue q (after waiting for it): its handle may be reused.