Module Sarek_execute.Execute

V2 Vector Argument Type

Re-export structured error types

type vector_arg =
  1. | Vec : ('a, 'b) Spoc_core.Vector.t -> vector_arg
    (*

    V2 Vector - expands to (buffer, length) for JIT

    *)
  2. | Int : int -> vector_arg
    (*

    Integer scalar

    *)
  3. | Int32 : int32 -> vector_arg
    (*

    32-bit integer scalar

    *)
  4. | Int64 : int64 -> vector_arg
    (*

    64-bit integer scalar

    *)
  5. | Float32 : float -> vector_arg
    (*

    32-bit float scalar

    *)
  6. | Float64 : float -> vector_arg
    (*

    64-bit float scalar

    *)

V2 Vector argument type - supports automatic transfers and length expansion. This is the main type-safe way to pass arguments to kernels.

val custom_value_to_bytes : 'a. 'a Spoc_core.Vector.custom_type -> 'a -> bytes
val custom_value_of_bytes : 'a. 'a Spoc_core.Vector.custom_type -> bytes -> 'a
val exec_arg_of_vector : 'a 'b. ('a, 'b) Spoc_core.Vector.t -> Spoc_framework.Framework_sig.exec_arg

Convert vector_arg list to exec_arg array (new typed interface). Creates EXEC_VECTOR wrappers for vectors.

val vector_args_to_exec_array : vector_arg list -> Spoc_framework.Framework_sig.exec_arg array
val get_device_buffer : ('a, 'b) Spoc_core.Vector.t -> Spoc_core.Device.t -> (module Spoc_core.Vector.DEVICE_BUFFER)

Retrieve device buffer for a vector on a specific device.

Returns a first-class module containing the device buffer's pointer, size, and binding function. The buffer must exist (typically created by a prior transfer).

  • parameter v

    Vector to get buffer from

  • parameter dev

    Device the buffer should be allocated on

  • returns

    Device buffer module

  • raises Transfer_failed

    if vector has no buffer on this device

val soa_dispatch : soa_abi:bool -> ('a, 'b) Spoc_core.Vector.t -> Spoc_core.Device.t -> Spoc_core.Vector.soa_binding option
val soa_param_names : Sarek_ir_types.kernel -> vector_arg list -> Spoc_core.Device.t -> string list
val transfer_vectors_to_device : ?soa_abi:bool -> ?settle:bool -> vector_arg list -> Spoc_core.Device.t -> unit

Transfer all V2 Vector args to device

val vector_length_arg : int -> Spoc_framework.Framework_sig.run_source_arg

The auto-injected vector length. Kernels index with int32, and Int32.of_int WRAPS silently above Int32.max_int — a 2^31-element vector would reach the kernel with a negative length. Refused instead (FR-018).

val expand_to_run_source_args : ?inject_lengths:bool -> ?soa_abi:bool -> vector_arg list -> Spoc_core.Device.t -> Spoc_framework.Framework_sig.run_source_arg list

Expand vector args to run_source_arg format.

  • parameter inject_lengths

    If true (default), auto-inject vector length as RSA_Vector_Length after each buffer. This matches Sarek-generated kernels which expect (ptr, len) pairs. Set to false for external kernels with different signatures.

The injected length is tagged RSA_Vector_Length, not RSA_Int32, so backends can tell it apart from a genuine caller-supplied scalar that happens to immediately follow a buffer (which is exactly what a ~inject_lengths:false caller can pass) - see Framework_sig.run_source_arg.

Execution Dispatch

val ir_elttype_of_vector_kind : 'a 'b. ('a, 'b) Spoc_core.Vector.kind -> Sarek_ir_types.elttype option

Launch-time argument check

The Vec constructor of vector_arg is existential (Vec : ('a, 'b) Vector.t -> vector_arg), so a vector's element type is ERASED the moment it enters an ~args list. No OCaml type constraint on the generated kernel closure can therefore catch passing a float32 vector where the kernel declared a float16 vector — the mismatch happens on a path where the types are already gone. Executed on gfx1100: such a launch compiled clean and read/wrote 2N bytes of a 4N-byte buffer, producing 1 2 0 0 for input 1 2 3 4, with the Native path catching it only by accident.

The IR is the one place where the DECLARED parameters and the SUPPLIED arguments meet, so the check lives here, and it covers every element type.

ARITY IS CHECKED FIRST, and is an error rather than a precondition for the rest. An earlier version ran the per-argument checks only if the counts matched, which made a wrong count silently disable every other check — the conservatism was also a bypass. Worse, a SHORT argument list is a memory-safety problem in its own right and independent of f16: both Cuda_api.Kernel.launch and Hip_api.Kernel.launch size the kernel-argument array with CArray.make (ptr void) (List.length args) and hand cuLaunchKernel / hipModuleLaunchKernel a bare CArray.start params with NO count (the trailing extra pointer is NULL). The driver then reads as many entries as the COMPILED signature declares, so a short list makes it read past the end of that array and dereference whatever it finds as a parameter — for a pointer parameter, an arbitrary device address. Rejecting the arity here is what keeps that unreachable.

Element types are compared exactly where the runtime kind has an IR counterpart. Where it does not, the check does NOT silently pass: it falls back to comparing PHYSICAL ELEMENT WIDTHS, which is the property that actually matters for buffer striding. That closes the wildcard: Vector.Char holds 1-byte elements while source char lowers to TInt32, so a Char vector is accessed through a 4-byte int*. (That lowering is PRE-EXISTING — Sarek_lower_ir.elttype_of_typ mapped TReg Char -> Ir.TInt32 before this branch — and is NOT fixed here; the check simply refuses to be the thing that hides it.)

Still conservative where it must be: a Custom (record/variant) element is nominal and both sides derive it from the same registered layout, so its element comparison is skipped deliberately rather than by omission.

val elttype_label : Sarek_ir_types.elttype -> string
val ir_scalar_width : Sarek_ir_types.elttype -> int option

Byte width of an IR element type, when it is a scalar. None for aggregates (whose width comes from the registered layout, not from this table).

val is_custom_kind : 'a 'b. ('a, 'b) Spoc_core.Vector.kind -> bool
val ir_elttype_of_scalar_arg : vector_arg -> Sarek_ir_types.elttype option

IR element type a SCALAR launch argument is tagged with on the host. Int and Int32 denote the same 32-bit slot. None for Vec, which is handled by the vector arms.

This tag is exactly what decides how many bytes the launch writes into the argument slot, so it is the host side of the same width contract the vector check enforces — see check_launch_args.

val arg_label : vector_arg -> string
val check_launch_bounds : kernel:string -> Sarek_ir_types.kernel -> block:Spoc_framework.Framework_sig.dims -> unit

Portable half of pragma ["sarek.max_threads N"]: refuse, before any backend sees the launch, a block whose thread count exceeds the declared bound. Applies to every backend, Native and Interpreter included, so the mistake is the same Sarek error everywhere rather than a driver failure on some of them. A kernel without a bound is never refused here.

val warp_width_of_device : Spoc_core.Device.t -> int

The warp width a kernel will run on, on device. A hardware device reports its own; Native and the interpreter simulate one, whose width is read at launch time (Sarek_warp.simulated_width) because it is the test hook of the parity tests and is not frozen into the device's capabilities.

val shuffle_key : bool Ir_memo.key
val kernel_uses_shuffle_memo : Sarek_ir_types.kernel -> bool
val check_launch_warp_width : kernel:string -> width:int -> Sarek_ir_types.kernel -> block:Spoc_framework.Framework_sig.dims -> unit

Refuse a launch whose kernel shuffles and whose block is not a whole number of warps (docs/plans/warp-shuffle-intake-2026-10-17.md, decision D3). The lanes of a partial warp would read sources that do not exist, which is undefined on CUDA, HIP and Metal; the rule is the same on every backend (Native and the interpreter included) so the mistake is one Sarek error everywhere. Kernels without a shuffle are never refused here. The error is Execute_error.error.Invalid_dimensions, the one check_launch_bounds raises for the sibling sarek.max_threads rule.

val check_launch_warp : device:Spoc_core.Device.t -> kernel:string -> Sarek_ir_types.kernel -> block:Spoc_framework.Framework_sig.dims -> unit
val check_launch_args : kernel:string -> Sarek_ir_types.kernel -> vector_arg list -> unit
val raise_capability_refusal : backend:string -> target:string -> Sarek_capability.verdict -> 'a

Render a non-permitting verdict and raise it as a Backend_error.

Shared by check_device_capabilities and check_interpreter_capabilities so the two launch gates cannot drift into describing the same refusal differently — which is a real hazard here, since backlog-154 was two gates disagreeing about the same kernel.

val check_device_capabilities : device:Spoc_core.Device.t -> Sarek_ir_types.kernel -> unit

Refuse a launch whose kernel needs a wide element type the target device does not provide (#142).

WHY THIS IS A LAUNCH GATE AND NOT A CODEGEN REFUSAL. These are Sarek_capability.kind.Device_optional capabilities: kind_needs_device Device_optional = true, and Framework_sig.generate_source takes no device, so codegen is structurally the wrong place to ask. The device is first in scope here, next to check_launch_args.

WHAT IT REPLACES. Nothing — that is the defect. Before #142 an int64 kernel on Vulkan reached vkCreateShaderModule with SPIR-V declaring OpCapability Int64 and no shaderInt64 enabled on the logical device. On an RX 7900 XTX (RADV, Mesa 26.1.4-arch3.1) that is not a crash and not a wrong answer: results are correct and the violation is visible only under VK_LAYER_KHRONOS_validation (VUID-VkShaderModuleCreateInfo-pCode-08740). Silent undefined behaviour on the driver that happens to cope is exactly the failure mode a capability model exists to convert into a diagnostic.

Routed through Sarek_capability.permits rather than a membership test so an Sarek_capability.verdict.Unknown verdict refuses instead of falling through to permitted.

val check_interpreter_capabilities : Sarek_ir_types.kernel -> unit

The launch gate for run_interpreter_vectors (backlog-154).

run_interpreter_vectors applied check_launch_args and NOT check_device_capabilities, so the interpreter — the cross-backend numeric oracle — was the one execution path with no capability gate on it. The asymmetry was observable: in one run of test_coopmat_integer_e2e on this workstation, check_device_capabilities printed launch gate refuses on CPU Interpreter (Sequential) for the same kernel run_interpreter_vectors then evaluated to 65536 bit-exact results.

Note what could NOT be done about that: calling check_device_capabilities here. It needs a Device.t, this entry point has none by design, and — the substantive half — the interpreter's Framework_sig.capabilities says coopmat = None, which means "not probed", which refuses. Wiring the device gate in would have taken the oracle offline rather than closed a hole. The bypass was load-bearing, which is why the fix is a capability answer for the interpreter and not a call to someone else's.

Sarek_interp_capability is that answer, and it lives beside the evaluator that justifies it. See its interface for the argument about what an interpreter should advertise.

Launch-preparation memo

Everything run does to a kernel before binding its arguments depends on the IR and not on the arguments: the device capability analysis (IR folds) and, on JIT backends, generating the kernel source. Measured on HIP (RX 7900 XTX), that work was 60-70 % of the host cost of a launch — 5.5 of 18 us for a 10-argument kernel, ~25 of ~40 us for a 2 KB matrix-vector kernel — and it was repeated identically on every launch of the same kernel. This memo performs it once.

KEYS. The IR by PHYSICAL identity: Sarek_ir_types.kernel is immutable and a PPX kernel hands the same body_ir record to every launch, so == is a sound and free equality. A structurally equal IR rebuilt per launch is simply a miss and runs exactly as before. Then, per IR:

WHAT IS NEVER MEMOISED. Failures: a refused capability check or a generate_source that raises or returns None stores nothing, so the next identical launch fails the same way. And everything that depends on the arguments — check_launch_args, transfers, argument expansion — still runs on every launch.

THE ONE THING IT DOES NOT TRACK. Codegen reads the global intrinsic and type registries. They are filled at module initialisation and only grow, and a kernel whose intrinsic is missing fails codegen (not memoised). Replacing an intrinsic's implementation AFTER a kernel using it has launched is not seen by that kernel until clear_launch_cache is called, nor is backend debug logging (first launch only). The POLICY is tracked (Policy_stamp): a later grant or restriction is honoured at once.

LIFETIME AND CONCURRENCY. The entry is a slot of the IR's Ir_memo record, which is weak in the IR, and its data never refers to the IR, so an entry dies with its kernel. One mutex guards reading and installing entries and their lists; the analysis and codegen run outside it, so two domains missing at once may both generate — the first installed source wins and both get it. An entry belongs to the generation it was made in; clear_launch_cache bumps the generation, which makes every entry stale (replaced on the IR's next launch), and a result computed across a clear is returned to its caller but not installed. The memo is cleared by Cache_hooks.notify_clear_all (every backend Kernel.clear_cache, Device.reset) and by every device-destroy notification.

type source_key = {
  1. codegen : ?block:Spoc_framework.Framework_sig.dims -> ?soa_params:string list -> Sarek_ir_types.kernel -> string option;
  2. block : Spoc_framework.Framework_sig.dims;
  3. soa_params : string list;
}
val same_source_key : source_key -> source_key -> bool
val find_source : kernel:string -> source_key -> (source_key * ('a * Policy_stamp.t)) list -> 'a option
type launch_entry = {
  1. generation : int;
    (*

    the launch_memo_generation it belongs to

    *)
  2. mutable sources : (source_key * (string * Policy_stamp.t)) list;
    (*

    source and policy stamp per source_key

    *)
  3. mutable verified : (int * Spoc_framework.Framework_sig.capabilities) list;
    (*

    (device id, physical capabilities record) that passed the check

    *)
}
val launch_key : launch_entry Ir_memo.key
val launch_memo_lock : Stdlib.Mutex.t
val launch_memo_generation : int Stdlib.ref
val clear_launch_cache : unit -> unit

Drop every memoised launch preparation: the next launch of every kernel re-runs its device capability check and regenerates its source. Needed only after replacing an intrinsic or type implementation that already-launched kernels use; backend cache clears and device teardown call it already.

val current_entry : Ir_memo.facts -> launch_entry option
val memo_entry : Ir_memo.facts -> launch_entry
val check_device_capabilities_memo : device:Spoc_core.Device.t -> Sarek_ir_types.kernel -> unit

check_device_capabilities, performed once per (IR, device id, physical capabilities record). A refusal raises every time.

val generate_source_memo : (module Spoc_framework.Framework_sig.BACKEND) -> block:Spoc_framework.Framework_sig.dims -> soa_params:string list -> Sarek_ir_types.kernel -> string option

B.generate_source, performed once per (IR, backend, block, soa_params). Every hit returns the same physical string, which also lets the backend's Compile_cache.make_key memo skip its digests. None and exceptions are not memoised.

val check_stream_launch : device:Spoc_core.Device.t -> Spoc_core.Transfer.stream option -> unit

Validate a launch on stream for device BEFORE anything is transferred: the stream must belong to device, and a JIT backend must implement stream launches — a launch the caller ordered on a stream must never run on the default queue instead. Direct/Custom backends (Native, Interpreter) execute synchronously, which satisfies any stream ordering.

Launch window

JIT launches with no ~stream on a backend whose window is enabled are deferred in window and issued at the next synchronisation point, possibly fused with independent launches (Launch_window, specs/launch-window.md). Argument checks, the capability check and source generation still happen in the launching call; device compilation and the driver launch happen when the launch is issued, so an error there is raised by the call that flushed the window.

module Window : sig ... end

Control and statistics of the launch window.

val prepare_jit : (module Spoc_framework.Framework_sig.BACKEND) -> ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> name:string -> source:string -> block:Spoc_framework__Framework_sig.dims -> grid:Spoc_framework__Framework_sig.dims -> shared_mem:int -> vector_arg list -> unit -> unit
val issue_jit : (module Spoc_framework.Framework_sig.BACKEND) -> ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> name:string -> source:string -> block:Spoc_framework__Framework_sig.dims -> grid:Spoc_framework__Framework_sig.dims -> shared_mem:int -> vector_arg list -> unit
val jit_source : (module Spoc_framework.Framework_sig.BACKEND) -> device:Spoc_core.Device.t -> name:string -> block:Spoc_framework.Framework_sig.dims -> soa_params:string list -> Sarek_ir_types.kernel -> string
val submit_deferred : (module Spoc_framework.Framework_sig.BACKEND) -> device:Spoc_core.Device.t -> ir:Sarek_ir_types.kernel -> block:Spoc_framework.Framework_sig.dims -> grid:Sarek_execute.Launch_window.Dims.dims -> shared_mem:int -> issue:(unit -> unit) -> vector_arg list -> unit
val run_jit : (module Spoc_framework.Framework_sig.BACKEND) -> ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> name:string -> ir:Sarek_ir_types.kernel Stdlib.Lazy.t option -> block:Spoc_framework.Framework_sig.dims -> grid:Sarek_execute.Launch_window.Dims.dims -> shared_mem:int -> vector_arg list -> unit
val run : ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> name:string -> ir:Sarek_ir_types.kernel Stdlib.Lazy.t option -> native_fn: (block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> Spoc_framework.Framework_sig.exec_arg array -> unit) option -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> vector_arg list -> unit

Execute a kernel on a device using the unified dispatch mechanism.

  • parameter stream

    Enqueue the launch on this stream instead of the default one (HIP, CUDA); the call may return before the kernel has run, and the argument vectors stay rooted until the stream or a later event completes. On Metal the launch is enqueued on the stream's own command queue (host access to its buffers waits for it); on Native/Interpreter it is synchronous. Refused with Feature_not_supported on other JIT backends, and with Invalid_argument when the stream belongs to another device. The capability check and, on JIT backends, source generation are done once per physical IR and reused by later launches (see the launch-preparation memo above and clear_launch_cache); argument checks run every time. Without ~stream, a JIT launch on a backend whose launch window is enabled (HIP, OpenCL, Vulkan, CUDA and Metal by default) is deferred until the next synchronisation point and may be fused with independent launches (Window, specs/launch-window.md). Its checks and source generation still run in this call, but a device compilation or driver launch failure is raised by the call that flushes the window.

  • parameter device

    Target device

  • parameter name

    Kernel name

  • parameter ir

    Sarek IR kernel (lazy, only forced for JIT backends)

  • parameter native_fn

    Pre-compiled native function (for Direct backends)

  • parameter block

    Block dimensions

  • parameter grid

    Grid dimensions

  • parameter shared_mem

    Shared memory size in bytes (default 0)

  • parameter args

    Kernel arguments as vector_arg list

  • raises Execution_error

    if execution fails

V2 Vector Execution Helpers

val mark_vectors_stale : vector_arg list -> Spoc_core.Device.t -> unit

Mark vectors as stale on CPU after kernel execution.

After a kernel modifies vector data on a device, we need to track that the CPU-side data is now stale. This ensures future CPU reads will trigger a device→CPU transfer.

Special cases:

  • Native backend: No-op (uses zero-copy shared memory, no staleness)
  • JIT backends: Always mark stale (Transfer module handles zero-copy checks)
  • OpenCL CPU: Mark stale, like the other JIT backends (a host-pointer buffer from Host_ptr_buffer is read back through map/unmap)
  • parameter args

    Arguments that may contain vectors

  • parameter dev

    Device that just executed the kernel

val vector_to_interp_array : 'a 'b. ('a, 'b) Spoc_core.Vector.t -> Sarek_interp.Sarek_ir_interp.value array

Convert a V2 Vector to interpreter value array.

Converts vectors of primitive types (int32, float32, etc.) to the interpreter's runtime value representation. Custom types are converted using registered type helpers from Sarek_type_helpers.

This enables the interpreter backend to execute kernels on CPU without requiring GPU infrastructure.

  • parameter vec

    Input vector of any type

  • returns

    Array of interpreter values matching vector contents

val interp_array_to_vector : 'a 'b. Sarek_interp.Sarek_ir_interp.value array -> ('a, 'b) Spoc_core.Vector.t -> unit

Copy interpreter value array back to V2 Vector.

After interpreter execution, this function copies the runtime values back into the typed vector representation. Performs type checking and conversion for each element.

  • parameter arr

    Array of interpreter runtime values

  • parameter vec

    Destination vector (must match type of values)

val run_interpreter_vectors : ir:Sarek_ir_types.kernel -> args:vector_arg list -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> parallel:bool -> unit

Run kernel via interpreter with V2 Vectors. Note: Interpreter works with IR params directly - one arg per param. Vectors map to ArgArray (length is intrinsic to array).

val run_vectors_plain : ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> ir:Sarek_ir_types.kernel -> args:vector_arg list -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> unit -> unit

Execute a kernel with V2 Vectors. Auto-transfers, dispatches to backend.

This is the main execution entry point for Sarek-generated kernels. It performs the complete execution pipeline:

1. **Transfer**: Move vectors to device (no-op for CPU backends) 2. **Dispatch**: Call appropriate backend's execution method 3. **Mark stale**: Update vector location tracking

The function automatically handles differences between execution models:

  • JIT backends: Generate source, compile, launch
  • Direct (Native): Call pre-compiled OCaml function
  • Custom (Interpreter): Walk IR and evaluate expressions
  • parameter stream

    Launch on this stream (see run). Auto-transfers of the arguments still happen synchronously before the launch; vectors already resident (e.g. device-only ones) cost nothing.

  • parameter device

    Target device (determines backend)

  • parameter ir

    Sarek IR kernel definition

  • parameter args

    Kernel arguments (vectors and scalars)

  • parameter block

    Thread block dimensions (e.g., (256, 1, 1))

  • parameter grid

    Grid dimensions (e.g., (4, 1, 1))

  • parameter shared_mem

    Optional shared memory size in bytes (default: 0)

val matrix_scalar : vector_arg -> int32 option
val interpreter_is_parallel : Spoc_core.Device.t -> bool
val run_specialised : ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> args:vector_arg list -> Matrix_specialise.specialised -> unit
val run_vectors_outcome : ?stream:Spoc_core.Transfer.stream -> ?precision:Sarek_coopmat.operand_precision -> ?explicit:bool -> device:Spoc_core.Device.t -> ir:Sarek_ir_types.kernel -> args:vector_arg list -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> unit -> Matrix_specialise.outcome

run_vectors that also returns what ran: the kernel as given, or a matrix-unit rewrite of it (Matrix_specialise). precision chooses the operand rounding of a float32 product (it must have been accepted). ~explicit:true is an explicit request, like Sarek_matrix.run: float products need the accuracy opt-ins only (not Sarek_coopmat.accept_automatic_float_rewrite), and the interpreter is rewritten too.

val run_vectors : ?stream:Spoc_core.Transfer.stream -> device:Spoc_core.Device.t -> ir:Sarek_ir_types.kernel -> args:vector_arg list -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> unit -> unit

Launch ir on device. When ir is a matrix product that matrix units can compute under the current restrictions and accuracy opt-ins, the rewritten kernel runs instead, writing exactly the same outputs (specs/matrix-pattern-dynamic.md).

val sync_vectors_to_cpu : vector_arg list -> unit

Sync all V2 Vector outputs back to CPU

Convenience Functions

Create 1D grid and block dimensions

val dims2d : int -> int -> Spoc_framework.Framework_sig.dims

Create 2D grid and block dimensions

val dims3d : int -> int -> int -> Spoc_framework.Framework_sig.dims

Create 3D grid and block dimensions

val grid_for_size : problem_size:int -> block_size:int -> int

Calculate grid size for a given problem size and block size

val grid_for : problem_size:int -> block_size:int -> Spoc_framework.Framework_sig.dims

Calculate 1D grid dimensions for a problem size

External Kernel Execution

type source_lang = Spoc_framework.Framework_sig.source_lang =
  1. | CUDA_Source
  2. | OpenCL_Source
  3. | PTX
  4. | SPIR_V
  5. | GLSL_Source

Re-export source language type

val supports_lang : Spoc_core.Device.t -> source_lang -> bool

Check if a device supports a given source language.

Different backends support different source languages:

  • CUDA: CUDA source (.cu), PTX
  • OpenCL: OpenCL source (.cl)
  • Vulkan: SPIR-V, GLSL source (.comp, .glsl)
  • Native/Interpreter: None (not JIT backends)
  • parameter dev

    Device to check

  • parameter lang

    Source language to query

  • returns

    true if device can compile and execute this language

val run_source : device:Spoc_core.Device.t -> source:string -> lang:source_lang -> kernel_name:string -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> ?inject_lengths:bool -> vector_arg list -> unit

Execute an external kernel from source code.

This function allows running pre-written GPU kernels (CUDA, OpenCL, PTX) directly without going through the Sarek DSL.

  • parameter device

    Target device

  • parameter source

    Kernel source code as string

  • parameter lang

    Source language (CUDA_Source, OpenCL_Source, PTX)

  • parameter kernel_name

    Name of the kernel function in the source

  • parameter block

    Block dimensions

  • parameter grid

    Grid dimensions

  • parameter shared_mem

    Shared memory size in bytes (default 0)

  • parameter inject_lengths

    If true (default), auto-inject vector length as Int32 after each buffer argument. Sarek-generated kernels expect (ptr, len) pairs. Set to false for external kernels that don't follow this convention.

  • parameter args

    Kernel arguments as vector_arg list

  • raises Execution_error

    if device doesn't support the source language

val load_source : string -> string

Load kernel source from a file

val detect_lang : string -> source_lang

Detect source language from file extension

val run_source_file : device:Spoc_core.Device.t -> path:string -> kernel_name:string -> block:Spoc_framework.Framework_sig.dims -> grid:Spoc_framework.Framework_sig.dims -> ?shared_mem:int -> ?inject_lengths:bool -> vector_arg list -> unit

Execute an external kernel from a file.

Loads pre-written GPU kernel source from a file and executes it. Useful for integrating hand-optimized kernels or using features not yet supported by the Sarek PPX.

Source language is auto-detected from file extension:

  • .cu → CUDA source
  • .cl → OpenCL source
  • .ptx → PTX assembly
  • .spv → SPIR-V binary
  • .comp / .glsl → GLSL compute shader

Example:

  (* Execute hand-written CUDA kernel *)
  Execute.run_source_file
    ~device:(Device.get_default ())
    ~path:"kernels/optimized_matmul.cu"
    ~kernel_name:"matmul_kernel"
    ~block:(16, 16, 1)
    ~grid:(64, 64, 1)
    [Vec a; Vec b; Vec c; Int32 1024l]
  • parameter device

    Target device

  • parameter path

    Path to kernel source file

  • parameter kernel_name

    Name of kernel function in source

  • parameter block

    Block dimensions

  • parameter grid

    Grid dimensions

  • parameter shared_mem

    Optional shared memory in bytes

  • parameter inject_lengths

    If true (default), inject vector lengths after buffer args

  • parameter args

    Kernel arguments

  • raises Invalid_file

    if file extension is not recognized