Module Ir.Schedule_outcome

type phase =
  1. | Transform
  2. | Hardware_limits
  3. | Backend_codegen
  4. | Backend_compile
  5. | Preflight
    (*

    Context.check_launch_bindings: the per-candidate part of a launch's pre-dispatch validation — out-of-range static bindings. It precedes Ir.Task.run, so classify_raw never makes one fatal (gh-ocannl-564): nothing was dispatched, the failure is the caller's to fix and retry, and the lineage stays usable. Inside a Launch-tagged boundary it was instead unattributable, hence fatal, hence a condemned lineage for a one-line user mistake.

    Deliberately NOT the lineage-wide part (Context.check_lineage_runnable: poisoned lineage, uninitialized inputs, unexecuted dependencies), which a search raises outside this region — it fails every candidate at once, so containing it would report a completed search that timed nothing (gh-ocannl-569).

    *)
  6. | Launch
  7. | Sync
val sexp_of_phase : phase -> Sexplib0.Sexp.t
val compare_phase : phase -> phase -> int
val equal_phase : phase -> phase -> bool
type resource =
  1. | Workgroup_threads
  2. | Workgroup_memory
  3. | Thread_scratch
  4. | Workgroup_x_extent
  5. | Workgroup_y_extent
  6. | Workgroup_z_extent
  7. | Grid_y_extent
  8. | Grid_z_extent
  9. | Device_memory

What a Resource_exceeded candidate asked too much of. One variant per launch dimension, although the grid pair shares the single Backend_intf.hardware_limits.max_grid_yz cap and the workgroup triple shares one Backend_intf.hardware_limits.max_workgroup_dims array: the dimensions are shrunk by different knobs — an over-.y grid by coarsening the row blocking, an over-.z grid by keeping batch loops serial, an over-.z workgroup by re-tiling the outermost annotated thread loop — and this is the rejection key an autotune search groups declines under (key_of_cause). The limit is shared where it is one hardware fact; the resource is distinct because it is a schedule-side dimension.

Workgroup_x_extent / Workgroup_y_extent / Workgroup_z_extent are per-dimension and therefore not implied by Workgroup_threads, which caps only the thread product: on CUDA a 2 x 2 x 128 workgroup is 512 threads (legal) with a .z of 128 against a cap of 64 (gh-ocannl-679).

Device_memory is the one resource that is not a launch dimension: the device refused a buffer allocation (Device_memory_exhausted, gh-ocannl-1326). Its requested is the bytes of the allocation the driver refused, its limit the driver's free bytes read right after the refusal (None where the backend cannot read them).

val sexp_of_resource : resource -> Sexplib0.Sexp.t
val compare_resource : resource -> resource -> int
val equal_resource : resource -> resource -> bool
type severity =
  1. | Expected
  2. | Compiler_bug
  3. | Configuration
    (*

    The toolchain configuration rejects any code, not this code (gh-ocannl-1350): an explicit compiler setting the compiler does not accept, a compiler command that names no compiler, a broken local toolchain. A user error, so exception_of_cause renders it as Utils.User_error.

    *)
  4. | Environment
    (*

    The host refused an artifact the compiler built, for a reason that is neither the code nor the configuration (gh-ocannl-1717): a code-signing or system-policy refusal at load, a library that is missing after a successful compile. Typically transient -- a policy daemon or a filesystem under load -- so rerunning usually succeeds. exception_of_cause renders it as Failure: the operation failed, but the caller misused nothing (which Invalid_argument would claim) and has nothing to fix (which Utils.User_error would).

    *)
val sexp_of_severity : severity -> Sexplib0.Sexp.t
val compare_severity : severity -> severity -> int
val equal_severity : severity -> severity -> bool
type execution_effect =
  1. | No_device_writes
  2. | Writes_may_have_occurred
val sexp_of_execution_effect : execution_effect -> Sexplib0.Sexp.t
val compare_execution_effect : execution_effect -> execution_effect -> int
val equal_execution_effect : execution_effect -> execution_effect -> bool
type cause =
  1. | Illegal_schedule of {
    1. check : string;
    2. detail : string;
    }
  2. | Unsupported of {
    1. feature : string;
    2. detail : string;
    }
  3. | Resource_exceeded of {
    1. resource : resource;
    2. requested : int;
    3. limit : int option;
    4. detail : string;
    }
  4. | Backend_rejected of {
    1. backend : string;
    2. stage : string;
    3. severity : severity;
    4. detail : string;
    }
  5. | Unclassified of {
    1. phase : phase;
    2. exn_constructor : string;
    3. detail : string;
    }
  6. | Seed_evicted of {
    1. family : string;
    2. detail : string;
    }
    (*

    A detected seed site the search declined to propose because a candidate-volume cap bound (gh-ocannl-541): the site was reachable and ranked, and lost only to the cap. Recorded in the decline census so a previously-proposed site that stops being proposed leaves a signal instead of vanishing. family names the seed family (e.g. "split_reduce").

    *)
  7. | Not_dispatched of {
    1. origin : string;
    2. detail : string;
    }
    (*

    A candidate the search refused to run on a GPU backend because it binds no hardware dimension (gh-ocannl-532): the whole routine would execute in one work-item. Nothing is wrong with the candidate — the backend's execution model is what rejects it — but it is a decline like any other, and leaving it out of the census made a GPU search that timed one candidate indistinguishable from one whose candidates all failed (gh-ocannl-543). origin names where the refusal happened: "baseline" (the serial baseline), "candidate" (a compiled candidate that degenerated to a serial form), or "beam_move" (a menu move pruned before compile because it provably cannot parallelize an already unparallelized incumbent).

    *)
val sexp_of_cause : cause -> Sexplib0.Sexp.t
val equal_cause : cause -> cause -> bool
type rejection_key =
  1. | Illegal_schedule_key of string
  2. | Unsupported_key of string
  3. | Resource_exceeded_key of resource
  4. | Backend_rejected_key of string * string * severity
  5. | Unclassified_key of phase * string
  6. | Seed_evicted_key of string
  7. | Not_dispatched_key of string
val sexp_of_rejection_key : rejection_key -> Sexplib0.Sexp.t
val compare_rejection_key : rejection_key -> rejection_key -> int
val equal_rejection_key : rejection_key -> rejection_key -> bool
val key_of_cause : cause -> rejection_key
val detail_of_cause : cause -> string
val exception_of_cause : cause -> exn
val dlopen_stage : string

The stage of a Backend_rejected raised when the host's dynamic loader refuses a kernel the compiler built. Compiler_bug for an undefined symbol or a missing dependency (gh-ocannl-1077); Environment for a code-signing or policy refusal, or a library gone by the time it is loaded (gh-ocannl-1717).

val artifact_missing_stage : string

The compiler reported success but its library did not appear before the timeout: an Environment rejection (gh-ocannl-1717).

val codesign_stage : string

The requested host signing step rejected the compiled library: a Configuration rejection, since the step runs only when cc_backend_verify_codesign asks for it (gh-ocannl-1728).

val toolchain_stage : string

The stage of a Configuration rejection at Backend_compile: the kernel failed to compile, and so did a trivial kernel under the same command and flags (gh-ocannl-1350).

val uncontainable : phase -> cause -> bool

Whether a typed cause must end a search rather than decline one candidate: protect makes it Fatal although it is typed. Exactly three post-compile host-boundary stages at Backend_link are uncontainable: artifact_missing_stage violates the successful compiler's output contract; codesign_stage rejects a host/toolchain prerequisite; dlopen_stage refuses a library the compiler built. The first two provide no evidence against a candidate's schedule (gh-ocannl-1142). The third can correlate with candidates: gh-ocannl-1045's missing -lm broke only kernels referencing libmvec, so absorbing it quietly favored slower candidates avoiding those symbols (gh-ocannl-1077). A JIT rejecting one candidate's PTX remains a counted decline. So is a compiler rejecting one candidate's source, except at toolchain_stage: a toolchain that compiles nothing would reject every candidate alike (gh-ocannl-1350).

type fatal = {
  1. exn : exn;
  2. backtrace : Stdlib.Printexc.raw_backtrace;
  3. phase : phase;
  4. candidate : string option;
  5. cause : cause option;
    (*

    The typed cause of a fatal failure that has one: an uncontainable cause, or one escalated by fatal_of_classified. None for an exception nothing classified.

    *)
}
type classified_cause = {
  1. phase : phase;
  2. cause : cause;
  3. execution_effect : execution_effect;
}

phase is where the failure was raised, not where the enclosing protect was installed: a narrow tag or a preserved cause reports its own phase, and a backend classifier's answer is pinned to the phase it was handed. This is what lets a report say whether a candidate died at link, at launch, or at sync.

val sexp_of_classified_cause : classified_cause -> Sexplib0.Sexp.t
val equal_classified_cause : classified_cause -> classified_cause -> bool
type failure =
  1. | Classified of classified_cause
  2. | Fatal of fatal
type 'a outcome = ('a, failure) Stdlib.Result.t
val fatal_of_classified : ?candidate:string -> classified_cause -> fatal

Escalates a classified rejection that cannot be contained after all — a launch failure whose execution effect is Writes_may_have_occurred, with no API to restore the damaged lineage. The cause is rendered into its public exception; the backtrace is the escalation site, since the original raise was already contained.

exception Cause_at of phase * cause
exception Raised_at of phase * exn * Stdlib.Printexc.raw_backtrace
exception Device_memory_exhausted of {
  1. backend : string;
  2. requested : int;
    (*

    Bytes of the one allocation the device refused.

    *)
  3. free : int option;
    (*

    The driver's free bytes, read after the refusal.

    *)
  4. detail : string;
    (*

    The driver's own report.

    *)
}

The device refused a buffer allocation (gh-ocannl-1326). Raised by a GPU backend's pool allocator in place of its driver's out-of-memory error, so that every allocation site — a compile's delta, a transfer's destination — reports it in one backend-independent shape. protect classifies it as a contained Resource_exceeded Device_memory decline with No_device_writes, whatever the backend's own classifier says (an allocation precedes any dispatch); Autotune.tune refuses a whole search when its baseline compile, one candidate's footprint, meets it.

type provenance =
  1. | Candidate
  2. | Cache_replay
  3. | Advisory
  4. | User_schedule
val sexp_of_provenance : provenance -> Sexplib0.Sexp.t
val compare_provenance : provenance -> provenance -> int
val equal_provenance : provenance -> provenance -> bool
val protect : ?strict:bool -> classify_backend:(phase -> exn -> classified_cause option) -> provenance:provenance -> phase:phase -> ?candidate:string -> (unit -> 'a) -> 'a outcome

Runs a phase boundary while preserving typed causes and raw backtraces. strict is exposed for policy tests; production callers should omit it and use strict_failure_classification.

val tag : phase -> (unit -> 'a) -> 'a

Tags an exception with the narrow phase where it was raised, preserving its raw backtrace. Existing typed transport exceptions pass through unchanged.

val raise_cause : cause -> _

Renders a typed internal cause using the exception contract of the existing public APIs.

val raise_failure : failure -> _

Re-raises a fatal failure with its original backtrace, or renders a classified cause.