Sarek_execute.Launch_windowDeferred launch window with horizontal fusion (specs/launch-window.md).
Execute submits JIT launches here instead of issuing them. Pending launches are kept in groups, in submission order. A new launch joins the newest pending group it is compatible with, provided it conflicts with no launch in that group or in any later one. Otherwise it opens a group of its own. A group of several launches is issued as one horizontally fused kernel (Fusion_horizontal); a group of one is the original launch.
Conflicts. Each launch carries the buffers its vector arguments bind and whether the kernel reads or writes each one (Kernel_access). A buffer is named by its vector's id: every device buffer belongs to exactly one vector (only Transfer.ensure_buffer installs one, in that vector's own table), so on one device two arguments share memory exactly when they are the same vector. Device addresses are not used: Vulkan reports none and OpenCL reports object handles. Two launches on the same device conflict when they bind the same buffer and at least one of the two accesses writes. A launch is never moved before a launch it conflicts with, so every conflicting pair is issued in submission order.
Flushing. flush issues every pending group. Execute installs it as the Spoc_core.Launch_hooks flusher, so every synchronisation point flushes before it acts. At most max_groups groups are pending: a new group beyond that issues the oldest one first.
Extension point. A launch is a plain record. A later stage that specialises a launch (for instance a launch-time kernel rewrite) can map launch values before submit without touching grouping or flushing.
This module depends only on the IR and Stdlib, so it builds for jsoo too.
module Dims = Spoc_framework.Framework_sigOne bound vector argument: its buffer (the vector's id) and how the kernel uses it.
type 'arg launch = {device_id : int;backend : string;ir : Sarek_ir_types.kernel;ir_id : int;max_grid_x : int;the device's grid-size limit in x
*)max_arg_slots : int;args : 'arg list;block : Dims.dims;grid : Dims.dims;accesses : access list;issue : unit -> unit;issue this launch now, unfused
*)issue_fused : Sarek_ir_types.kernel -> 'arg list -> Dims.dims -> unit;issue a fused kernel on this launch's device with this launch's block, the given arguments and grid
*)}type ir_info = {ir_id : int;param_accesses : (string * Kernel_access.access) list;fusable : (int, string) Stdlib.result;shared bytes, or why not fusable
*)}type 'arg group = {g_device : int;mutable rev_members : 'arg launch list;newest first
*)mutable size : int;mutable grid_x : int;sum of the members' grid x
*)}val key_of : 'a group -> int listtype stats = {submitted : int;JIT launches submitted through Execute
issued : int;backend launches actually made
*)fused_groups : int;groups of two or more launches issued fused
*)fused_built : int;distinct fused kernels built
*)fused_refused : int;member sequences refused (build or launch failed)
*)}type 'arg t = {lock : Stdlib.Mutex.t;mutable groups : 'arg group list;oldest first
*)mutable epilogue : 'arg t -> 'arg launch -> bool;called under lock before grouping: true when the launch was merged into a pending launch as its epilogue (Launch_epilogue)
int32_arg : int32 -> 'arg;arg_buf : 'arg -> int option;the vector id a vector argument binds
*)arg_int32 : 'arg -> int32 option;the value of an int32 scalar
*)set_pending : int -> unit;guard : (unit -> unit) -> unit;info_key : ir_info Ir_memo.key;this window's slot of each IR's facts
*)mutable next_ir_id : int;cache : (int list, Sarek_ir_types.kernel) Stdlib.Hashtbl.t;member ir_ids -> fused kernel
*)refused : (int list, unit) Stdlib.Hashtbl.t;member sequences never fused
*)c_submitted : int Stdlib.Atomic.t;c_issued : int Stdlib.Atomic.t;c_groups : int Stdlib.Atomic.t;c_built : int Stdlib.Atomic.t;never reset: it also names fused kernels without a content name
*)c_refused : int Stdlib.Atomic.t;}val create :
int32_arg:(int32 -> 'arg) ->
arg_buf:('arg -> int option) ->
arg_int32:('arg -> int32 option) ->
set_pending:(int -> unit) ->
guard:((unit -> unit) -> unit) ->
'arg tcreate ~int32_arg ~set_pending ~guard makes an empty window. int32_arg builds a scalar argument for the fused kernel's position parameters. set_pending is told the number of pending launches after each change. guard f runs f (the issuing of pending groups) with the synchronisation hooks disabled on this domain. Each window has its own slot in the Ir_memo record of every IR it sees; the slot lives as long as that IR, not as long as the window.
include module type of struct include Window_gate endtype refusal = Window_gate.refusal = | Switched_offExecute.Window.set_enabled false
| RestrictedSAREK_OPT_DISABLE=window
| Cpu_devicehost-pointer buffers alias host memory, and distinct vectors can alias each other through it, so buffer identity is no longer the vector's
*)| Not_on of string| Streameda launch on a stream
*)| Soa_paramsstructure-of-arrays arguments
*)Why a launch is not deferred, in the order the reasons are checked.
Backends deferred by default (specs/launch-window.md); Native, Interpreter and WebGPU stay immediate. set_backend_enabled opts in or out.
val refusal :
framework:string ->
is_cpu:bool ->
streamed:bool ->
soa_params:'a list ->
refusal optionrefusal ~framework ~is_cpu ~streamed ~soa_params: None when a JIT launch on a device of framework is deferred.
include module type of struct include Launch_limits endKernel argument slots a launch's signature may use on backend: Metal binds every argument at one of 31 [buffer(n)] indices. A backend with no limit configured here is unbounded as far as the window is concerned.
Set the argument slot limit of backend (for tests and for backends whose limit becomes known).
Bytes of push constants a launch's signature may use on backend: Vulkan declares one 128-byte range in every pipeline layout and refuses a launch whose block is larger. A backend with no limit configured here is unbounded as far as the window is concerned.
Set the push-constant byte limit of backend (for tests and for backends whose limit becomes known).
val push_bytes_ok : backend:string -> Sarek_ir_types.decl list list -> booltrue when a group whose members have these parameter lists keeps its kernel's push-constant block within backend's limit: the fused kernel's block for two or more members, the launch's own for one. Answers at once when the backend has no limit.
val ir_info_locked : 'a t -> Sarek_ir_types.kernel -> ir_infoAs ir_info, for a caller that already holds w.lock.
val ir_info : 'a t -> Sarek_ir_types.kernel -> ir_infoval joinable : 'a launch -> boolSlots one launch's arguments take in a kernel signature: a vector is a buffer plus its length, a scalar one slot.
Slots of the signature of the fused kernel of members: every member's arguments plus (offset, grid size) per member.
true when members, issued as one group, stay within the backend's argument slots: the fused signature for two or more, the launch's own signature for one.
val params_of : 'a launch -> Sarek_ir_types.decl listval push_ok : 'a launch list -> boolThe group's push-constant block fits the backend (push_bytes_ok).
val refuse : 'a t -> int list -> unitval fused_kernel :
'a t ->
int list ->
'b launch list ->
Sarek_ir_types.kernel optionval count_pending : 'a group list -> intval flush : 'a t -> unitIssue every pending launch, in order. The pending count stays non-zero until they are all issued, so a synchronisation point reached meanwhile on another domain waits for this flush (on w.lock) instead of going ahead (FR-007).
Defer l. When l needs a new group and the window is full, the oldest group is issued first; if that raises, l is not submitted and the exception propagates (the launching call fails before its own launch is accepted, so its vectors' state is unchanged).
val touches : 'a t -> device_id:int -> buf:int -> booltrue when a pending launch on device_id binds vector buf.
val count_immediate : 'a t -> unitCount a launch that bypassed the window (issued immediately).
val reset_stats : 'a t -> unitval clear_cache : ?also:(unit -> unit) -> 'a t -> unitForget every fused kernel and refusal (the next fused group rebuilds), and run also in the same critical section.
val set_enabled : 'a t -> bool -> unitval set_backend_enabled : 'a t -> backend:string -> bool -> unit