Module Tnode.Placements

Per-context placement resolution

The context-scoped side of the memory-mode split (docs/proposals/context-scoped-memory-modes.md): memory_mode on the tnode is *declared intent* -- requests made at graph-construction time (user set_materialized, parameter/constant marking, op-support Never_virtual) -- while the *decisions* (resolving to Virtual / Local / On_device) are recorded here, per compilation lineage. A Placements table rides Low_level.optimize_ctx: it is copied at the start of each backend compile, so sibling compiles from the same context are hermetic (a candidate compile cannot poison another's placement resolution), while child contexts inherit the lineage's decisions.

Lookups fall back to the tnode's intent when the lineage has not yet decided; updates apply the same lattice as update_memory_mode but never write the tnode. Intent strengthened after a lineage compiled does not invalidate that lineage.

module Key : sig ... end
type nonrec t = {
  1. table : (t, memory_mode * Base.int) Base.Hashtbl.t;
}
val sexp_of_t : t -> Sexplib0.Sexp.t
val create : unit -> t
val copy : t -> t
val get : t -> t Base.Hashtbl.key -> (memory_mode * Base.int) Base.option

The effective placement state: the lineage's decision if any, otherwise the tnode's declared intent. Side-effect free.

val update : t -> t Base.Hashtbl.key -> memory_mode -> Base.Int.t -> unit
val promote_local_to_device : t -> t Base.Hashtbl.key -> Base.int -> unit

Kernel-fission escape hatch: strengthen a routine-scoped Local decision to On_device. Local is only ever a compiler decision (never declared intent), premised on the node's lifetime being confined to one kernel launch; when the schedule layer splits a routine into multiple kernels at a point where the node's live range crosses, that premise is withdrawn and the node must own a context buffer. The update lattice deliberately rejects Local -> On_device (decisions are final within a lineage); this is the one sanctioned override, sound exactly because fission runs between optimization and code generation — before any consumer of the decision (codegen parameter lists, context allocation) has read it.

val raw_entry : t -> t Base.Hashtbl.key -> (memory_mode * Base.int) option

Fission bookkeeping around promote_local_to_device: the raw lineage entry (no intent fallback), captured before a promotion so unsafe_restore can undo it when segment coalescing removes the crossing that motivated it. Undoing is sound in that direction only: schedules computed under the (stricter) materialized view remain valid for a Local node, while the converse would miss coverage requirements.

val unsafe_restore : t -> t Base.Hashtbl.key -> (memory_mode * Base.int) option -> unit
val debug : t -> t Base.Hashtbl.key -> Base.String.t
val default_to_most_local : t -> t Base.Hashtbl.key -> Base__Int.t -> unit

Mirrors the retired tnode-level default_to_most_local, resolving into the placements table, with two observation guards (docs/proposals/context-scoped-memory-modes.md):

  • An observable node never defaults to Local: Local is the unobservable class, and unlike Virtual it cannot be served by recomputation.
  • An observable node still undecided (None) at a forcing point materializes instead of defaulting to Virtual: a node that stayed undecided through optimization has no tracked computation (the virtualizer commits every stored candidate at cleanup), so a Virtual resolution would be unobservable in practice -- there would be nothing to recompute from. Effectively_constant keeps folding to Virtual: observation of constants is served by their registered host-init data.
val is_virtual_force : t -> t Base.Hashtbl.key -> Base.int -> bool
val is_materialized_force : t -> t Base.Hashtbl.key -> Base__Int.t -> bool
val is_in_context_force : t -> t Base.Hashtbl.key -> Base__Int.t -> bool
val is_materialized_peek : t -> t Base.Hashtbl.key -> Base.bool

Pure counterpart of is_materialized_force; see Tnode.is_materialized_peek.

val known_not_materialized : t -> t Base.Hashtbl.key -> bool
val known_constant : t -> t Base.Hashtbl.key -> bool
val known_non_virtual : t -> t Base.Hashtbl.key -> bool
val known_virtual : t -> t Base.Hashtbl.key -> bool
val mode_is_unspecified : t -> t Base.Hashtbl.key -> bool