Module Ir.Tnode

module Lazy = Utils.Lazy
module Nd = Ndarray
val _get_local_debug_runtime : Base.unit -> (module Minidebug_runtime.Debug_runtime)
type memory_mode =
  1. | Effectively_constant
    (*

    A constant, or a subset of Virtual.

    *)
  2. | Virtual
    (*

    The tensor node's computations are inlined on a per-scalar basis. The node has no buffer in any context; its defining computation is tracked (in Low_level.optimize_ctx), so it remains observable: its value can be recomputed on demand, by later routines and by host reads (Context.to_host, gh-ocannl-777). Observability is inductive, not intrinsic: recomputation requires the nodes the tracked computation reads to be observable themselves, so a Virtual node that depends -- directly or transitively through other Virtual nodes, with no materialized node between -- on a Local node inherits its unobservability (Context.observability reports the path).

    *)
  3. | Never_virtual
    (*

    An as-yet-unresolved request; resolves to Local or On_device.

    *)
  4. | Local
    (*

    Routine-scoped scratch: the tensor node exists only for the duration of a single call to a compiled function, stored to whatever degree the optimizer decides on (e.g. a stack array), and is not persisted across calls. It is not materialized (owns no context buffer) and is not available for merging across devices. Unlike Virtual -- which stays observable via recomputation -- Local is unobservable, and the sole source of unobservability: its computation is not tracked, and compiling a later routine that reads it raises a User_error directing to mark it as materialized before its first use. This mode is only ever assigned by the compiler (never requested), reserving the freedom to optimize placement of nodes whose lifetime is confined to one routine.

    *)
  5. | On_device
    (*

    The tensor node is stored on the devices that compute with it and persisted across function calls. It is available for merging across devices (for devices that support merging / P2P). CPU-side access (printing, persistence, inspection) is on-demand via context-mediated device-to-host transfers; no host copy is stored on the node.

    *)
val sexp_of_memory_mode : memory_mode -> Sexplib0.Sexp.t
val equal_memory_mode : memory_mode -> memory_mode -> Base.bool
type provenance =
  1. | Visit_cap
    (*

    Per-cell visits above virtualize_max_visits, or an uncovered read. One of the three heuristic caps of Low_level.decide_placements: policy priors, which the decision-vector search may flip back to inlining, unlike a legality or observability verdict.

    *)
  2. | Inline_reduction_cap
    (*

    Reduction extent above virtualize_max_inline_reduction.

    *)
  3. | Inline_fanin_cap
    (*

    Transitive fan-in above virtualize_max_inline_fanin.

    *)
  4. | Read_before_write
    (*

    An uncovered read within the routine: the node is an input, so it owns a device buffer whose prior contents are preserved. Minted from two sites -- the lenient verdict of decide_placements and the strict re-classification in reconcile_traced_store.

    *)
  5. | Scope_local
    (*

    A scope local the inliner minted, committed Virtual by cleanup.

    *)
  6. | Surviving_read
    (*

    A read that survived inlining, so its target must be materialized. Cleanup is the commitment point, not a re-assertion: a node read here but written only under a virtualized setter is decided right now.

    *)
  7. | Site of Base.string
    (*

    A decision no other code reads back, explained by its own tag. Keeping these out of the constructor list is what stops this type from growing a case per backend query site.

    *)
  8. | Refined of provenance * provenance
    (*

    A decision that defaulted an earlier one into a concrete placement (Placements.default_to_most_local): the prior decision, then the one that resolved it. Renders as "39:inline-reduction-cap -> 432:is-local-materialized-query" -- what the retired 1000 * prior + own arithmetic encoded as the undecodable 39432.

    *)

Why a memory mode was requested or decided (gh-ocannl-609).

Two kinds of tag, and the split is a layering decision, not a convenience. A decision that only ever gets recorded is a Site carrying its own explanation: the ~60 of those are minted across nine modules, from the C renderer's storage queries to the scheduler's tile placements, and giving each a constructor would make this type -- which sits at the bottom of the dependency graph, since Placements is here -- enumerate the vocabulary of every module above it. A tag that some other code reads back gets a constructor instead, so the reading is an exhaustive match rather than a string comparison that can silently stop matching.

A Site's string is spelled "<code>:<kebab-case-reason>", and so is every constructor's rendering: the numeric code is the integer this used to be, kept so that issues, comments and goldens citing e.g. Non_virtual 13 or "provenance 39" still resolve. Codes are not unique -- two sites that were the same integer stay the same integer and are told apart by their reasons. Those collisions are pinned by test/operations/provenance_tag_inventory, which derives every tag from this type and its minting sites and refuses a new collision: a new tag takes a number that inventory's golden does not list.

val sexp_of_provenance : provenance -> Sexplib0.Sexp.t
val equal_provenance : provenance -> provenance -> Base.bool
val provenance_to_string : provenance -> Base.string
val leading_provenance : provenance -> provenance

The decision the later ones refined: the leftmost tag of a Refined chain, which Placements.default_to_most_local builds left-nested.

type delayed_prec =
  1. | Default of Ops.prec
  2. | Inferred of Ops.prec Lazy.t
  3. | Specified of Ops.prec
val sexp_of_delayed_prec : delayed_prec -> Sexplib0.Sexp.t
val prec_write_hook : (Base.unit -> Base.unit) Base.ref
module Prec_cell : sig ... end

A node's delayed precision, sealed (gh-ocannl-1624) together with the storage precision it settles into: read with get, written only by the guarded update_prec / update_infer_prec -- which update_prec and update_infer_prec wrap -- each write that changes the value reported to prec_write_hook. storage is the node's storage_prec, forced from the value; once it is forced, update_prec only validates and update_infer_prec refuses. The cell owns that lazy, so no caller can hand the guard a different one.

type bounds_state =
  1. | Bounds_unknown
    (*

    No proposal yet: readers fall back to the precision's machine range.

    *)
  2. | Bounds_proposed of Interval.t
    (*

    Join of all proposals so far.

    *)
  3. | Bounds_settled of Interval.t
    (*

    Consumed by a guard fold; subsequent proposals must fit or raise.

    *)

Per-tensor scalar value bounds: the interprocedural layer of the interval analysis (docs/proposals/interval-analysis-scalar-t.md), the third instance of the delayed_prec propose/settle lifecycle. Writers propose bounds (joined, like Inferred promotion); a reader that discharges a guard against the candidate settles it (like forcing the prec lazy); a post-settlement proposal that does not fit the settled interval is an error (like update_prec on a settled precision) -- otherwise already-generated code that folded a bounds guard away would become unsound.

Phase B v1 execution anchoring (binding constraint 3): every compiled device write of a node proposes Interval.top at lowering time (see Low_level.optimize_proc), so a candidate can only be narrower than top for host-initialized, never-device-written tensors; guard folds therefore never depend on device-computed values, sidestepping the runs-never/runs-later/runs-repeatedly hazards.

val sexp_of_bounds_state : bounds_state -> Sexplib0.Sexp.t
val default_namespace : string
val validate_namespace : Base.String.t -> unit

Namespaces must be legal C-family identifiers so they can prefix generated-code identifiers and debug names verbatim (rendered as ns__n42); see docs/proposals gh-ocannl-372.

val current_namespace : string Base.ref
val set_current_namespace : Base.String.t -> Base.unit
val get_current_namespace : unit -> string
type t = {
  1. storage_prec : Ops.prec Lazy.t;
  2. dims : Base.int Base.array Lazy.t;
  3. padding : (Ops.axis_padding Base.array * Base.float) Base.option Lazy.t;
    (*

    If the tensor node is pre-padded, this is the pair of (left padding, right padding) per axis and the padding/neutral value. Both the margins and the neutral value are part of the node's identity: they commit when this lazy is forced (the node's first compilation), the margins permanently hold the neutral value, and later conflicting demands — more padding, or margin-touching operations expecting a different neutral — are rejected at shape-inference time.

    *)
  4. size_in_bytes : Base.int Lazy.t;
  5. id : Base.int;
    (*

    The within-session id ("s_id" of gh-ocannl-372): consecutive number for nodes created in the current session, restarting at 0 on Tensor.unsafe_reinitialize. Full node identity for presentation and persistence is the pair (namespace, id); in-memory identity is uid.

    *)
  6. namespace : Base.string;
    (*

    The namespace qualifying id, stamped at creation from the ambient current_namespace unless created with an explicit ?namespace (persistence loads, schedule-internal nodes). Grads share their value node's session namespace. Elided from sexps and renderings when it is default_namespace.

    *)
  7. uid : Base.int;
    (*

    Process-unique identity, from a counter that no reinitialization ever resets -- unlike id, which restarts at 0 on Tensor.unsafe_reinitialize for deterministic printing. All comparison/hashing (hence every tnode-keyed map, set and cache in the process) uses uid, so a stale entry surviving a reinitialization can never alias a fresh tnode that reuses its id. Excluded from sexps to keep debug output reinitialization-deterministic.

    *)
  8. label : Base.string Base.list;
    (*

    Display information. It is better if the last element of the list is the most narrow or alphanumeric, e.g. an identifier.

    *)
  9. delayed_prec_unsafe : Prec_cell.t;
    (*

    Participates in the computation of storage_prec. Sealed (gh-ocannl-1624): read with Prec_cell.get (or get_specified_prec), written only by update_prec and update_infer_prec, which guard it and count each write.

    *)
  10. mutable bounds : bounds_state;
    (*

    Scalar value bounds summary; see bounds_state for the lifecycle.

    *)
  11. mutable memory_mode_intent : (memory_mode * provenance) Base.option;
    (*

    The tnode's declared intent -- requests made at graph-construction time (parameter and constant marking, Train.set_materialized, op-support Never_virtual), paired with a provenance tag saying which request it was. Since the context-scoped memory-modes split (docs/proposals/context-scoped-memory-modes.md) this is monotone, side-effect free to read, and never written by the compilation pipeline: placement decisions are per-compilation-lineage, recorded in Placements tables riding Low_level.optimize_ctx.

    *)
  12. mutable observable : Base.bool;
    (*

    Declared host-observation intent (docs/proposals/context-scoped-memory-modes.md category 2): someone intends to read this node's values (printing, persistence, inspection). Monotone-upward (set, never cleared) and side-effect free to read. Observation does not require materialization -- a Virtual resolution stays observable via recomputation -- so the only constraint this imposes on placement is: do not resolve the node into the Local-dependent unobservable class (Placements.default_to_most_local resolves Never_virtual to On_device rather than Local for observable nodes).

    *)
  13. mutable context_owned : Base.bool;
    (*

    The node can be mutated independently in each context (a model parameter). Even when a routine only reads it, it must use working storage rather than a constant pool (cached per root, or per device for a host literal). Set at graph construction and never cleared.

    *)
  14. mutable host_constant : Base.bool;
    (*

    Declared value-constancy: the node's values are fixed at construction and always equal its registered host-init data (an ndarray-backed literal). Set by Tensor.ndarray (and eligible loaders), never cleared. This carries the constancy that Effectively_constant intent cannot: ndarray-backed nodes are minted On_device (provenance Site "49:ndarray-backed"), so the memory-mode lattice has no room left for the constant marking. Consumed by Schedule.Stage's hoisted packing (gh-ocannl-470) to justify materializing a repacked copy once per device.

    *)
  15. mutable zero_valid : Base.bool;
    (*

    Declared accumulator semantics: allocation-time zero is a correct value for this node, so Context.run may bind it as a READ-ONLY input that nothing has written (gh-ocannl-1462). Set at creation of every gradient node (by Tensor) and by mark_zero_valid, never cleared. It is permission to consume the zeros a fresh buffer holds, not evidence that a buffer was written: it does not excuse an unwritten read-before-write input, nor any other node aliasing or slicing this one.

    *)
  16. mutable alias_of : (t * Indexing.static_symbol) Base.option;
    (*

    When Some (parent, batch_idx), this node is a zero-copy slice-alias *view* of parent: it owns no buffer of its own, and every read/write of it is redirected (during lowering) to parent with batch_idx prepended as the leading index. Set by Assignments.lower for alias-eligible Fetch.Slices; orthogonal to memory_mode_intent. The strong reference to parent also keeps it reachable for as long as the alias is.

    *)
  17. mutable slice_of : (t * Indexing.static_symbol) Base.option;
    (*

    When Some (parent, batch_idx), this node is an \@| sub-tensor slice of parent. Set *eagerly at construction* (independent of alias eligibility), so it is a superset of alias_of: every confirmed alias is a slice, but an ineligible slice (precision- converting, padded, virtual parent) is still a slice that falls back to a materializing copy. Used to reject direct host access (read/write the parent instead) -- including the window before lowering decides eligibility, where alias_of is still None.

    *)
  18. mutable backend_info : Base.Sexp.t;
  19. mutable code_name : Base.string Base.option;
}
val sexp_of_t : t -> Sexplib0.Sexp.t
val compare : t -> t -> Base.int
val reserved_uid_base : Base__Int.t
val next_uid : int Base.ref
val next_reserved_uid : Base__Int.t Base.ref
val reserved_uid_depth : int Base.ref
val fresh_uid : unit -> Base__Int.t
val with_reserved_uids : (unit -> 'a) -> 'a

Runs f with node uids allocated from the reserved range (see fresh_uid).

val session_gate : (Base.unit -> Base.unit) Base.ref
val validity_check : (t -> Base.unit) Base.option Base.ref
val num_elems : t -> Base__Int.t
val size_in_bytes_peek : t -> Base__Int.t

tn's size in bytes at its storage precision — the value tn.size_in_bytes forces to — computed WITHOUT forcing that lazy. header prints it, so the header reports a property of the node rather than whether some code path happened to force the lazy (gh-ocannl-1369). A query meant to leave the node as it found it — a schedule-time estimate one backend alone runs, such as Schedule.thread_private_bytes or Schedule.workgroup_memory_bytes — reads this too, so no observer of the lazy's state can tell which backend ran.

val dims_without_padding : t -> Base.int Base.array
val get_padding : t -> (Ops.axis_padding Base.array * Base.float) Base.option
val ident_prefix : Base.String.t -> Base.String.t
val id : t -> Base.String.t
val label : t -> Base.String.t
val is_alphanum_ : Base.String.t -> Base.bool
val no_grad_ident_label : Base.String.t Base.List.t -> bool * Base.String.t option

no_grad_ident_label label is whether label names a gradient, and its identifier: the label's identifier components (operator labels such as *. dropped) joined by _ with the numeric components last, a leading "grad" peeled off as the gradient flag, and a _ prepended to a result that starts with a digit; None when no identifier component remains. The one source of both the minted heuristic identifier (styled_ident) and the pre-minting display name (get_debug_name).

val get_debug_name : ?code_name:??? -> ?namespace:??? -> id:Base__Int.t -> label:Base.String.t Base.List.t -> unit -> Base.String.t

get_debug_name ?code_name ~id ~label () is the code name when one was minted (_grad read as .grad). Otherwise it is the name the label spells -- label_name of a node with this id and label (gh-ocannl-1686): the identifier of no_grad_ident_label with .grad for a gradient, and n<id> (a gradient: its value's id) when the label has no identifier component. So before and after minting one scheme names a node.

val debug_name : t -> Base.String.t

debug_name tn is a display name, not an identity. Before any renderer has minted an identifier for tn it is label_name (gh-ocannl-1686). Once one has (update_code_name) it is that code name, and the name can change over the process: a node whose label another node of the same routine also carries is minted as n<id>_<label>, and that form sticks. Which renderers run depends on configuration -- with output_debug_files_in_build_directory the .cd/.ll printers name nodes the C emitter never asks about, such as a merge-buffer source -- so the same program can report different debug names (gh-ocannl-1552). To find a node by name, compare label_name (or label).

val debug_memory_mode : (memory_mode * provenance) option -> Base.String.t
val log_debug_info : from_log_level:int -> t -> unit
val most_local_materialized_mode : t -> memory_mode

The mode Never_virtual resolves to when defaulted: Local if the node fits the stack threshold, On_device otherwise.

val is_observable : t -> Base.bool
val set_observable : t -> unit

Declares host-observation intent (see observable). Monotone: there is no unset.

val is_alias : t -> bool

A slice-alias view (see alias_of). Such a node owns no buffer of its own; its accesses are redirected to its parent during lowering.

val alias_of : t -> (t * Indexing.static_symbol) Base.option
val set_alias_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unit

Marks tn as a zero-copy slice-alias view of parent with leading index batch_idx. Idempotent when re-marked with the same parent.

val is_slice : t -> bool

Whether tn is an \@| sub-tensor slice (see slice_of) -- set eagerly at construction, independent of alias eligibility. A superset of is_alias.

val slice_of : t -> (t * Indexing.static_symbol) Base.option
val set_slice_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unit

Marks tn as an \@| slice of parent eagerly at construction (before alias eligibility is known). Idempotent.

val known_not_materialized : t -> bool
val known_constant : t -> bool
val known_host_constant : t -> Base.bool

Whether tn's values are declared fixed at construction, forever equal to its registered host-init data (see host_constant). Includes known_constant intent: small constants keep the Effectively_constant marking (their creation does not pass through the On_device-minting ndarray-backed path).

val set_context_owned : t -> unit

Declares that tn needs independently mutable storage in each context. Monotone: set, never cleared.

val set_host_constant : t -> unit

Declares that tn's values are fixed at construction (host-init-backed literal). Monotone: set, never cleared.

val mark_zero_valid : t -> unit

Declares that allocation-time zero is a correct value of tn (see zero_valid): a routine may then read it, read-only, before anything writes it. Gradients are marked at creation; mark any other read-only accumulator the same way.

val is_zero_valid : t -> Base.bool
val known_non_virtual : t -> bool
val known_virtual : t -> bool
val mode_is_unspecified : t -> bool
val transition_memory_mode : debug_name:Base.string -> (memory_mode * provenance) option -> memory_mode -> provenance -> memory_mode * provenance

The pure memory-mode lattice transition, shared between update_memory_mode (tnode-level declared intent) and per-context placement resolution (Placements). Returns the new state; raises on conflicting requests.

val update_memory_mode : t -> memory_mode -> provenance -> unit
val update_prec : ?only_if:??? -> t -> Ops.prec -> Base.unit
val update_infer_prec : ?only_if:??? -> t -> Ops.prec Lazy.t -> Base.unit
val get_specified_prec : t -> Ops.prec option
type open_snapshot = {
  1. snap_node : t;
  2. snap_prec : Prec_cell.snapshot;
  3. snap_bounds : bounds_state;
  4. snap_memory_mode_intent : (memory_mode * provenance) Base.option;
  5. snap_observable : Base.bool;
  6. snap_context_owned : Base.bool;
  7. snap_host_constant : Base.bool;
  8. snap_zero_valid : Base.bool;
  9. snap_alias_of : (t * Indexing.static_symbol) Base.option;
  10. snap_slice_of : (t * Indexing.static_symbol) Base.option;
  11. snap_backend_info : Base.Sexp.t;
  12. snap_code_name : Base.string Base.option;
}

Checkpoint snapshots of open nodes (gh-ocannl-903)

A checkpoint records every mutable field of each node the open work allocated, and a rewind puts them back: the precision links, memory-mode intents, ownership and observability a later construction (or Tensor.param over an open initializer) wrote. The record is destructured exhaustively, so a new field of t is a compile error here until the snapshot accounts for it. The lazies are not snapshotted: dims, padding and size_in_bytes are forced only through shape inference's committed-state reads, which a rewind's validity check watches, and storage_prec is the precision cell's, whose settling open_snapshot_settled reports.

val snapshot_open : t -> open_snapshot
val open_snapshot_node : open_snapshot -> t
val open_snapshot_settled : open_snapshot -> Base.bool

Whether the node's precision began settling after the snapshot: the rewind cannot reset it.

val restore_open : open_snapshot -> unit
val open_snapshot_differences : open_snapshot -> Base.String.t Base.List.t

The fields of the node that differ from the snapshot (physically), by name: a diagnostic.

Value-bounds lifecycle (see bounds_state)

val propose_bounds : what:Base.String.t -> t -> Interval.t -> unit

Joins iv into the node's candidate bounds. Post-settlement, instead validates that iv fits the settled interval and raises Utils.User_error otherwise -- a wider write after a reader already folded a guard against the bounds would leave compiled code unsound. what names the proposing write for the error message.

val pin_bounds_top : what:Base.String.t -> t -> unit

Pins the candidate to Interval.top: Phase B v1 execution anchoring for compiled device writes. Post-settlement (of non-top bounds) this raises like any non-fitting proposal.

val bounds_candidate : t -> Interval.t option

The current candidate (or settled) bounds, if any proposal was made. Readers should intersect with the precision's machine range themselves.

val settle_bounds : t -> unit

Marks the candidate as consumed by a guard fold. Idempotent; raises if nothing was ever proposed (folds must only consume existing candidates).

val bounds_scan_worthwhile : Ops.prec -> bool

Whether host-upload scans can profit for this precision: integer storage only -- the current consumers (gather-guard discharge, index-width selection) act on integer-valued facts, and the Phase A folding policy ignores float bounds (binding constraint 8: gate the O(n) scans).

val scan_host_bounds : Ops.prec -> Nd.t -> Interval.t
val propose_bounds_from_host : t -> Nd.t -> unit

Scans a host buffer about to initialize tn's device data and proposes the observed bounds (pre-settlement) or validates them (post-settlement). Call on every host-write path (Context.from_host-mediated writes and link-time Host_inits uploads). No-op for float and opaque precisions, and when the candidate is already pinned to top.

include sig ... end
type comparator_witness
val comparator : (t, comparator_witness) Base__Comparator.comparator
val equal : t -> t -> Base.bool
val hash : t -> Base__Ppx_hash_lib.Std.Hash.hash_value
val hash_fold_t : Base__.Ppx_hash_lib.Std.Hash.state -> t -> Base__.Ppx_hash_lib.Std.Hash.state
val hash_t : t -> Base__Ppx_hash_lib.Std.Hash.hash_value
module Comp : sig ... end
module Placements : sig ... end
type t_set = Base.Set.M(Comp).t
val sexp_of_t_set : (t, 'a) Base.Set.t -> Sexplib0.Sexp.t
val dims_to_string : ?with_axis_numbers:??? -> t -> Base.String.t
val label_name : t -> Base.String.t

label_name tn is the identifier the default (heuristic) style mints for tn when no other node of its routine carries the same label: the label's identifier components (an initializer's operator label such as *. dropped, numeric components last), a leading "grad" as a .grad suffix, and n<id> for a label with no identifier component. It is computed from the label alone, so unlike debug_name -- which equals it until a code name is minted, and takes the n<id>_ form once a routine repeats the label -- it never changes over the process, and it is the name to find a node by (gh-ocannl-1606). label is the raw join, operator labels included.

val styled_ident : repeating_nograd_idents:(Base.String.t, 'a) Base.Hashtbl.t -> repeating_grad_idents:(Base.String.t, 'a) Base.Hashtbl.t -> [< `Heuristic_ocannl of [< `Dot_grad | `Under_grad ] | `Name_and_label | `Name_only ] -> t -> Base.String.t
val update_code_name : t -> Base.string -> unit
val get_style : ?arg_name:??? -> ?no_dots:??? -> unit -> [> `Heuristic_ocannl of [> `Dot_grad | `Under_grad ] | `Name_and_label | `Name_only ]
val header : t -> string
module Registry : sig ... end
val registry : Registry.t
val validate_padded_numel_contract : id:Base__Int.t -> label:Base.String.t Base.List.t -> Base.int Base.array -> unit
val create : ?namespace:??? -> delayed_prec -> id:Base__Int.t -> label:Base.String.t Base.List.t -> unpadded_dims:Base__Int.t Base.Array.t Lazy.t -> padding:(Ops.axis_padding Base.array * Base.float) Base.option Lazy.t -> unit -> t
val create_from_padded : ?namespace:??? -> id:Base__Int.t -> label:Base.String.t Base.List.t -> ndarray:Nd.t -> padding:(Ops.axis_padding Base.array * Base.float) Base.option -> unit -> t * Nd.t lazy_t
val create_with_reshape : id:Base__Int.t -> label:Base.String.t Base.List.t -> base_ndarray:Nd.t -> unpadded_dims:Base__Int.t Base.Array.t Lazy.t -> padding:(Ops.axis_padding Base.array * Base.float) Base.Option.t Lazy.t -> from_padded:bool -> unit -> t * Nd.t lazy_t
val initial_default_prec : Ops.prec
val find_namespaced : namespace:Base.string -> id:Base.int -> Registry.data option
val find : id:Base.int -> Registry.data option

Accessors

Host-side value access (get_value, set_value, get_values, set_values, points_1d, points_2d) now lives in Context: it requires an explicit context and performs an on-demand device-to-host transfer. There is no host copy stored on the tensor node.

val before_accessibility_snapshot : (Base.unit -> Base.unit) Base.list Base.ref
val print_accessible_headers : ?pred:??? -> unit -> Base.unit
val log_accessible_headers : ?pred:??? -> unit -> unit