Module Sarek_opencl.Sarek_ir_opencl

Re-export the OpenCL code generator from the pure sarek_codegen library. Consumers of Sarek_opencl.Sarek_ir_opencl and in-package Sarek_ir_opencl are unchanged.

include module type of struct include Sarek_codegen.Sarek_ir_opencl end

Local error module — same raised exception as the package-level Opencl_error.

val bad_arity : string -> int -> int -> 'a

Raise a located invalid-argument-count error (atomic-arity helper for the shared Dispatch.emit_atomic).

Constants

val small_buffer_size : int

Buffer size for small temporary string buffers

val large_buffer_size : int

Buffer size for large code generation buffers

val format_float : float -> string

Format float with full precision (17 digits for double precision)

type state = Sarek_codegen.Sarek_ir_opencl.state = {
  1. variants : (string * (string * Sarek_ir_types.elttype list) list) list;
}

Everything one run of generate_with_types needs to know that is not reachable from the IR node it is currently emitting. It is a VALUE threaded through the emit functions, not module state, and that is the whole point of backlog-185/200: the field below used to be a module-level ref, so a second generation — on another domain, or simply a later one after Sarek_transpile had written it — read the first one's value.

variants is the kernel's own kern_variants, read by the SMatch arm to recover a constructor's payload types. Derived from the kernel, so it could be re-derived at each use site; it is carried here because that is where the ref it replaces was read from.

It is the ONLY field. A mid-refactor draft also carried the framework tag, threaded in as ?framework so a caller could pick the registry spelling; that was dropped once it was clear every caller passed this backend its own name. The record survives the shrink deliberately — a one-field record is the honest shape for "one run's state", and the next thing that needs threading has somewhere to go.

The state for emitting k.

Type Mapping

val mangle_name : string -> string
val opencl_type_of_elttype : Sarek_ir_types.elttype -> string

Map Sarek IR element type to OpenCL C type string

val opencl_memspace : Sarek_ir_types.memspace -> string

Map memory space to OpenCL qualifier

val opencl_param_type : Sarek_ir_types.elttype -> string

Map Sarek IR element type to OpenCL C type for kernel parameters

Thread Intrinsics

val opencl_thread_intrinsic : string -> string

Expression Generation

val gen_expr : 'a -> Stdlib.Buffer.t -> Sarek_ir_types.expr -> unit
val gen_binop : Sarek_ir_types.binop -> string
val gen_unop : Sarek_ir_types.unop -> string
val opencl_backend : 'a -> Sarek_ir_types.expr Dispatch.spec

L-value Generation

val gen_lvalue : 'a -> Stdlib.Buffer.t -> Sarek_ir_types.lvalue -> unit

Statement Generation

val gen_stmt : state -> Stdlib.Buffer.t -> string -> Sarek_ir_types.stmt -> unit
val gen_match_case : state -> Stdlib.Buffer.t -> string -> string -> Sarek_ir_types.pattern -> Sarek_ir_types.stmt -> unit

Generate a pattern match case (extracted helper)

val gen_array_decl : state -> Stdlib.Buffer.t -> string -> Sarek_ir_types.var -> Sarek_ir_types.elttype -> Sarek_ir_types.expr -> Sarek_ir_types.memspace -> Sarek_ir_types.stmt -> unit

Generate array declaration with optional __local qualifier (extracted helper)

Declaration Generation

val gen_param : Stdlib.Buffer.t -> Sarek_ir_types.decl -> unit
val gen_local : 'a -> Stdlib.Buffer.t -> string -> Sarek_ir_types.decl -> unit

Helper Function Generation

val gen_helper_signature : Stdlib.Buffer.t -> Sarek_ir_types.helper_func -> unit

Emit ret name(params) — shared by the prototype and the definition so the two can never drift apart.

val gen_helper_proto : Stdlib.Buffer.t -> Sarek_ir_types.helper_func -> unit

Forward declaration for a helper.

Emitted for every helper BEFORE any definition, because kern_funcs carries no ordering guarantee: a caller listed before its callee produced error: use of undeclared identifier 'g' — invalid OpenCL C that depended purely on list order. Found by the #128 sweep once the recursion classifier stopped refusing helper-to-helper calls; no golden kernel has helpers, which is why the corpus never showed it. Declaring all of them up front makes the order irrelevant rather than relying on the IR producer to topologically sort.

val gen_helper_func : state -> Stdlib.Buffer.t -> Sarek_ir_types.helper_func -> unit

Generate a helper function (OpenCL device function)

val gen_helpers : state -> Stdlib.Buffer.t -> Sarek_ir_types.helper_func list -> unit

Prototypes for every helper, then the definitions. A no-op when the kernel has no helpers, so kernels without them are byte-identical to before.

Kernel Generation

val reject_float16_kernel : Sarek_ir_types.kernel -> unit
val reject_coopmat_kernel : Sarek_ir_types.kernel -> unit

Recursion Resolution

OpenCL C forbids recursion outright (OpenCL C 1.2 §6.9.e, 3.0 §6.9.5: "the OpenCL C programming language does not support recursion"). Unlike an undeclared identifier, no compiler in this project's reach diagnoses it: clang -x cl -cl-std=CL1.2 -fsyntax-only accepts a recursive device function silently, and rusticl/radeonsi (Mesa) does not diagnose it either — it overflows its own compiler stack inside libRusticlOpenCL and takes the host process down with SIGSEGV (~30 800 recursive frames on a clctxworker thread, zero OCaml frames in the backtrace). That crash is backlog #53, and its cause is this: pragma ["sarek.inline N"] bounds the UNROLLING, not the recursion, so the PPX leaves a residual self-call in the IR and this backend used to print it verbatim.

Emitting a self-call and hoping the vendor rejects it is therefore not an option, and neither is a blanket refusal: pragma ["sarek.inline N"] is an advertised feature that the PTX backend already lowers correctly. So this pass takes the same two-part policy as PTX (Sarek_ir_ptx_expr.emit_app_recursive), which keeps one semantics for one pragma across backends:

  • A self-recursive helper carrying pragma ["sarek.inline N"] has its residual self-calls replaced by a typed zero. The pragma is the author's contract that N levels cover every runtime input, so the residual call site is dynamically unreachable: it only has to be well-formed OpenCL C, never correct. Dropping the arguments is sound because IR expressions are pure by construction (see Sarek_ir_types.expr), so there are no argument side effects to lose — PTX has to evaluate them only because its lowering emits into a register file.
  • Any other cycle — a recursive helper with no pragma, or mutual recursion between helpers, which the PPX's self-call-only inliner cannot bound anyway — is REFUSED with a located error, the way Sarek_ir_inline_vec.splice_call refuses recursion for GLSL/WGSL.

A partial unroll that silently leaves a self-call is the one outcome ruled out: it is neither bounded nor refused.

Typed zero for the result of a residual (dynamically unreachable) recursive call. Aggregates are zeroed field-by-field / through the first constructor, so the emitted expression is a real value of the helper's return type.

val helper_inline_budget : Sarek_ir_types.helper_func -> int option

Inline budget declared by hf, parsed from an SPragma at its body root.

Deliberately a copy of Sarek_ir_ptx_expr.helper_inline_budget rather than a shared symbol: both are minimal re-implementations of the option parsing in Sarek_tailrec_pragma.parse_sarek_inline_pragma (the source of truth for the "sarek.inline N" string format), which lives in the PPX — a separate library not linkable from codegen. Factoring the two copies together belongs with moving the parser into the IR, not with this fix.

Bottom-up rewrite of every expression node reachable from a statement. One traversal serves both the call-graph scan (with f the identity plus a side effect) and the residual-call elision.

val called_helpers : string list -> Sarek_ir_types.stmt -> string list

Names of helper functions called (directly) from s.

val reachable : (string * string list) list -> string -> string list

Every helper name reachable from start through the call graph edges.

val refuse_recursion : string -> string -> 'a
val resolve_recursive_helpers : Sarek_ir_types.kernel -> Sarek_ir_types.kernel

Replace residual self-calls in budgeted self-recursive helpers by a typed zero, and refuse every other cycle. Post-condition (asserted below): the returned kernel's helper call graph is acyclic, so no EApp this backend emits can ever be a recursive call.

val generate_with_types : types:(string * (string * Sarek_ir_types.elttype) list) list -> Sarek_ir_types.kernel -> string

Generate OpenCL source with custom type definitions.

Registry lookups are made under the constant tag "OpenCL". An earlier draft of backlog-185/200 threaded a ?framework argument here so a caller could override it; it was dropped, because every caller passed this backend its own name — exactly the value the constant already is — and the one consumer that would have distinguished them, SNative, now refuses outright.

val generate : Sarek_ir_types.kernel -> string

Generate complete OpenCL source for a kernel.

A special case of generate_with_types with the kernel's OWN type declarations, which is the only thing every production caller ever passed: ~types has exactly the type of the kern_types field (Sarek_ir_types.kernel), so the parameter was redundant with the record it travels in. This used to be a separate 30-80 line copy of the emit sequence that silently omitted record typedefs, variant typedefs and the kernel's variants — source referencing an undeclared struct, with no error. Delegating keeps one emit path per backend.

val generate_with_fp64 : Sarek_ir_types.kernel -> string

Generate OpenCL source with double precision extension if needed