Module Utils

type settings = {
  1. mutable log_level : Base.int;
  2. mutable debug_log_from_routines : Base.bool;
    (*

    If the debug_log_from_routines flag is true _and_ the flag log_level > 1, backends should generate code (e.g. fprintf statements) to log the execution, and arrange for the logs to be emitted via ppx_minidebug.

    *)
  3. mutable output_debug_files_in_build_directory : Base.bool;
    (*

    Writes compilation related files in the build_files subdirectory of the run directory (additional files, or files that would otherwise be in temp directory). When both output_debug_files_in_build_directory = true and log_level > 1, compilation should also preserve debug and line information for runtime debugging.

    *)
  4. mutable fixed_state_for_init : Base.int Base.option;
  5. mutable print_decimals_precision : Base.int;
    (*

    When rendering arrays etc., outputs this many decimal digits.

    *)
  6. mutable check_half_prec_constants_cutoff : Base.float Base.option;
    (*

    If given, generic code optimization should fail if a half precision FP16 constant exceeds the cutoff.

    *)
  7. mutable default_prng_variant : Base.string;
    (*

    The default variant of threefry4x32 PRNG to use. Options: "crypto" (20 rounds) or "light" (2 rounds). Defaults to "light" for better performance.

    *)
  8. mutable large_models : Base.bool;
    (*

    If true, use uint64 for indexing arithmetic. If false, use uint32 for indexing arithmetic. This affects all backends' kernel index parameters and local index variables, and gates the per-pool offset width (uint32 caps a pool at 4 GB; see the pool allocator). Decoupled in intent from element indexing within a single tensor, though both currently follow it.

    *)
}
val settings_of_sexp : Sexplib0.Sexp.t -> settings
val sexp_of_settings : settings -> Sexplib0.Sexp.t
val settings : settings
val accessed_global_args : Base.String.t Base.Hash_set.t
val str_nonempty : f:(Base.String.t -> 'a) -> Base.String.t -> 'a option
val pair : 'a -> 'b -> 'a * 'b
val known_config_keys : (Base.String.t, Base.String.comparator_witness) Base.Set.t

Cache-identity classification of the configuration keys (gh-ocannl-572)

type config_key_class =
  1. | Aggregate
    (*

    Selects values for other keys rather than acting itself; those keys carry the classification.

    *)
  2. | Code_borne
    (*

    Changes the lowered code or its placements, hence the canonical digest (Ir.Schedule_cache.digest, the "digest" key component) that every cache key starts with. Nothing to add: the code is the identity.

    *)
  3. | Keyed of Base.string
    (*

    Invisible to the lowered code, so it has to be carried explicitly — by this named component of Ir.Schedule_cache.key_components. This is the class gh-ocannl-568 was an omission from.

    *)
  4. | Search_shaping
    (*

    Shapes which schedule a search proposes, times or crowns, but not what a crowned one means or how fast it then runs: a saved schedule carries its own ops and replay re-derives nothing from these. Two processes differing only here may find different winners; each is a valid winner for the other.

    *)
  5. | Execution_neutral
    (*

    Host-side behavior only: logging, debug artifacts, directories, validation and error reporting, allocation layout, launch mechanics. Nothing a kernel does depends on it.

    *)

What a configuration key does to the identity a compilation cache keys on. A cache replays a schedule crowned by an earlier process onto freshly lowered code, and the only thing between "replay" and "replay from another regime" is that identity: gh-ocannl-568 measured a default-flags run replaying a tf32-tuned winner at 5.9x slower than not tuning at all, because the numerics policy was absent from the key. So every key is classified here, and test/operations/digest_completeness fails on one that is not — adding a knob forces the question "does this change what a cached schedule means?" while the answer is still fresh.

val config_key_classification : (config_key_class * Base.string * Base.string Base.list) Base.list

The classification of every key in known_config_keys, grouped by class and by reason. The reason is the reviewable part: the class alone does not say why, and why is what a reviewer has to check when a key is added to a group or moved between two.

val classify_config_key : Base.string -> (config_key_class * Base.string) option

The class and the reason recorded for key, or None when it is unclassified (which test/operations/digest_completeness rejects for every known key).

val bool_of_config_string : arg_name:Base.String.t -> Base.String.t -> bool
val log_config_sourcing : bool Base.ref

Whether to print where each configuration setting comes from (commandline, environment, config file, or the hard-coded default). It gates the logging of the config-reading functions themselves, so it is bootstrapped directly rather than via get_global_arg: the initial value only reflects the commandline and the environment, and the config file setting is applied once the config file is read. Can also be set programmatically, e.g. to trace configs a test reads.

Opt-in (gh-ocannl-595): the trace is a debugging tool -- some eighty lines on a run that reads a config file -- and it shares stderr with the unknown-config-key warning, the one startup message that means the user made a mistake. Enabling it traces every key, without a second dependence on log_level: it says "tell me where the configuration came from", and answering that for one key is not a useful reading of it.

val env_var_name : Base.String.t -> Base.String.t

The environment spelling of a config key: OCANNL_ followed by the key in uppercase, and nothing else. The prefix is mandatory here (unlike on the commandline and in a config file) so that OCANNL does not read an unrelated tool's variable.

ONE spelling (gh-ocannl-652). A lowercase ocannl_<key> used to be read as well, and won over the uppercase one besides -- which test/operations/profiles/dune had to document as a real precedence trap. It bought nothing anyone could name: no caller in this repository spelled a variable that way and no documentation recommended it, while every dune rule that has to declare the ambient variables it is invalidated by paid two lines per key, 228 of them, forever (gh-ocannl-628). The reserved namespaces this file introduced alongside them (OCANNL_TOOL_..., OCANNL_LOG_LEVEL_<MODULE>) were uppercase-only from the start, for the same reason: uppercase is what the shell convention is unambiguous about.

Dropping a spelling someone may have exported is a silent demotion, so the spellings this file no longer reads are not merely ignored: classify_env_var reports one as Env_unread_spelling, and the check at the foot of this file makes it FATAL when it names a known key. See unread_env_vars.

Dashes are not a spelling either: ocannl-log_level and its uppercase form were dropped in gh-ocannl-605, documented nowhere and used by nobody, while costing every such dune rule four spellings per key -- of which the natural-looking all-dashed ocannl-log-level was never one. Dashes remain idiomatic on the commandline, where cmdline_var_names accepts them.

val env_var_reserved_prefixes : string list

The ocannl-prefixed environment namespaces that are deliberately NOT configuration, so that a name in one of them is never reported as a misspelt key (gh-ocannl-629).

Two of them, and each is a namespace someone else reads:

  • ocannl_tool_… belongs to this repository's own tooling and test harnesses -- tools/sweep.sh's state directory, tools/test-run.sh's time cap, the hardware hook in test/operations/test_cpu_topology.ml. None of them is a library setting, and every one of them is exported into the environment of processes that link OCANNL.
  • ocannl_log_level_… is read by ppx_minidebug at PREPROCESSING time: the per-module tracing gates, one %%global_debug_log_level_from_env_var at the top of tensor/row.ml and of its eighteen siblings. The name after the prefix is a module, not a setting, and it is consumed before this file's initialization exists to have an opinion.

A reserved prefix rather than a list of exempt names: a list is a second place to update when a tool grows a variable, nothing forces the update, and the failure mode is a warning the reader cannot act on -- which trains people to ignore the warning that matters, the exact outcome gh-ocannl-595 was fixed to prevent. A prefix costs the tooling a rename once and nothing thereafter.

val env_names_case_insensitive : bool

Whether the platform resolves environment variable names case-insensitively. On Windows ocannl_Log_level and OCANNL_LOG_LEVEL are ONE variable, so a spelling this file would call unread there is in fact read, and reporting it would be wrong on that platform only.

Native Windows only, NOT Cygwin (Codex P1 on PR #389): a Cygwin runtime sets Sys.cygwin rather than Sys.win32, and its POSIX environment is case-SENSITIVE -- getenv "OCANNL_BACKEND" does not find ocannl_backend there. Folding case on that runtime would call the lowercase spelling read while read_env_var found nothing, which is the silent demotion this whole check exists to prevent, delivered by the check itself. What matters is what the runtime's own getenv does, not whether the host kernel is Windows.

The DEFAULT of a parameter, rather than the only answer (gh-ocannl-661). Baked in from Sys.win32 at definition, it made the Windows reading of classify_env_var unreachable on every other host: Linux and macOS CI could not execute that branch, so a change collapsing it stayed green on the ordinary PR path and surfaced days later on a scheduled Windows run -- which is how the dashed spellings came to be classified as read there in the first place. Every caller in this file still takes the platform's answer; what the parameter buys is that test/operations/config_var_spellings pins BOTH readings on EVERY host.

type env_var_class =
  1. | Env_not_addressed
    (*

    not ocannl-prefixed: someone else's variable entirely

    *)
  2. | Env_reserved of Base.string
    (*

    in a reserved non-configuration namespace, named by its prefix

    *)
  3. | Env_config_key of Base.string
    (*

    a spelling read_env_var reads, of that key

    *)
  4. | Env_unread_spelling of Base.string
    (*

    a known key, spelled in a way nothing reads: dashed, or not fully uppercase where case matters. A configuration ERROR rather than a warning -- see unread_env_vars.

    *)
  5. | Env_unread_reserved of Base.string
    (*

    in a reserved namespace, in a casing its reader does not consult

    *)
  6. | Env_unknown_key of Base.string
    (*

    addressed to the configuration, naming no key

    *)

What an environment variable name is, to OCANNL. The classification is shared by the startup warning at the foot of this file and by test/operations/env_var_deps, which asks it of every (env_var …) a dune file declares -- so a name a rule tracks and a name a run warns about are decided by one function.

Which family a name is in, and then -- for both families alike -- whether THIS spelling of it is one its reader actually consults. The second question is the one that carries the feature: an unread spelling is invisible in exactly the way a typo is, and answering it for keys while waving reserved names through would suppress the warning precisely where the name looks most like it should work (Codex P2 on PR #371). Each family has its own reader, so each answers it its own way -- env_var_name for a key, and uppercase for a reserved name, which is what the shell scripts and the %%global_debug_log_level_from_env_var arguments spell. Since gh-ocannl-652 those two answers are the same one: uppercase, everywhere under the prefix.

Case, and only case, collapses on Windows: the environment folds ocannl_backend onto OCANNL_BACKEND there, so the same name is read on one platform and not the other. It does NOT fold punctuation -- getenv "OCANNL_LOG_LEVEL" finds nothing set as ocannl-log_level on any platform -- so same_env_name compares the whole name rather than answering "true" to every candidate wherever the environment is case-insensitive (Codex P1 on PR #389). The earlier form classified every dashed spelling as read on Windows, which is where the dashed spellings this file's goldens lean on would have stopped being unread.

A key's separators are normalized before the known-key lookup, and only for it: OCANNL_PRINT-DECIMALS-PRECISION names print_decimals_precision recognizably, so it is an unread SPELLING of a known key -- fatal, per unread_env_vars -- rather than an unknown key that would warn and let the run continue on the default (Codex P2 on PR #389). The dashes are idiomatic on the commandline, where cmdline_var_names reads exactly this shape, which is why someone writes one here. What the normalization does not do is make it a spelling: the canonical name it is compared against is still the undashed one.

val same_env_name : ?case_insensitive:??? -> Base.String.Caseless.t -> Base.String.Caseless.t -> bool

Whether two environment variable names denote the same variable on this platform: case-folded where the environment is, and otherwise exactly. case_insensitive defaults to env_names_case_insensitive and is passed explicitly only by the test that pins both readings on every host -- see there for why the platform's answer is not the only one reachable.

val classify_env_var : ?case_insensitive:??? -> Base.String.t -> env_var_class
val unread_env_vars : unit -> (Base.String.t * bool * Base.String.t) Base.List.t

Every environment variable that addresses OCANNL's configuration and that nothing reads: the name, whether it is fatal, and the reason, in the order the names sort.

Fatal for a known key spelled in a way nothing reads, and a warning for everything else (gh-ocannl-652). The distinction is whether a VALUE that was meant to decide something is being dropped: ocannl_backend=cuda names a real key, so somebody wrote it to choose a backend, and silently running on the default instead is the failure mode this whole check exists to prevent -- it is also what dropping the lowercase spelling would otherwise have inflicted on anyone who had exported one. A name that matches no key (OCANNL_BACKEDN) never decided anything to begin with, and a lowercase name in a reserved namespace belongs to a tool rather than to the configuration; both stay warnings, as they were.

An EMPTY value is not reported at all, in either class: "" counts as unset at every source (see read_env_var), a dune rule clears a variable by setting it empty, and a launcher expanding $OCANNL_BACKEND with nothing set must not thereby abort the run.

Separate from the loop that consumes it (at the foot of this file) so that the walk is a value rather than an effect: what is reported is then a list something else -- a test, a tool refusing to run under a misconfigured environment -- can also ask for. Sorted so that a stream capturing the messages does not depend on the order the C library hands the environment over.

val cmdline_var_names : ?qualified_only:??? -> Base.String.t -> Base.String.t Base__List.t

The commandline spellings of a config key, up to the value separator: the ocannl_-qualified ones -- then, unless qualified_only, the prefix-free ones.

Every spelling carries a leading dash, one or two. A bare ocannl_log_level=1 used to be read as well, and gh-ocannl-605 dropped it (Codex P2 on PR #363): a bare argument is a host application's positional, and an OCANNL-linked tool taking a path -- ocannl_config is the obvious one -- was one key name away from having it eaten. It also left the unknown-argument warning with a spelling it could not diagnose, since a bare argument is exactly what it must NOT claim to know about.

The dashing is two independent choices, not one per separator: the prefix separator dashes on its own, and the key's own separators dash TOGETHER. For log_level that is ocannl_log_level, ocannl_log-level, ocannl-log_level and ocannl-log-level (each in lowercase and in uppercase, each with one leading dash or two). A key dashed halfway (ocannl-print_decimals-precision) is not a spelling -- and, since cmdline_var_prefixes is also what the unknown-argument warning matches, it is reported as unknown rather than silently ignored, which is what makes the narrower contract safe to have (Codex P2 on PR #363). Enumerating every separator independently is the alternative, at 2^separators spellings per key; nothing asked for it.

qualified_only exists because OCANNL is a library: it scans the host executable's Sys.argv, so a prefix-free key claims an application's own option of that name. That is tolerable for keys nobody else would spell (--virtualize_max_visits) and not for --profile, which is a common application flag and which OCANNL treats as fatal when it does not name a known bundle -- a host passing --profile=prod would die during module initialization (Codex P2 on PR #291).

val cmdline_var_prefixes : ?qualified_only:??? -> Base.String.t -> Base.String.t Base.List.t

What an argument setting n begins with: a spelling from cmdline_var_names followed by the value separator, which is _, -, = or nothing at all. Whatever remains of the argument is the value.

This is the single source of truth for "an argument OCANNL reads", and the unknown-argument warning at the bottom of this file matches against it rather than parsing arguments a second way. It used to parse: split on =, normalize every dash to an underscore, look the result up -- which accepted spellings the reader ignored (`--ocannl-log-level=1`, before gh-ocannl-605 made it real) and rejected ones the reader honoured (`--ocannl_log_level_1`, whose separator is not an =, so the key came out as `log_level_1`). Both directions are silent contradictions: one applies nothing while saying nothing, the other applies the setting while warning that it is unknown. One table cannot disagree with itself.

val qualified_only_config_keys : (Base.String.t, Base.String.comparator_witness) Base.Set.t
val read_cmdline_var : ?qualified_only:??? -> Base.String.t -> (Base.String.t * Base.String.t) option

The commandline sublevel of get_global_arg: returns the setting's value and the Sys.argv element it came from. Pure -- the sourcing log lives at the resolution seam, which is the only place that knows which sublevel actually won.

qualified_only defaults per key from qualified_only_config_keys, so a caller need not remember which keys renounce their prefix-free spellings.

val cmdline_arg_is_config_key : Base.String.t -> bool

Whether a raw command-line argument addresses a known configuration key under any spelling read_cmdline_var accepts — prefixed or prefix-free, dashed or underscored, any separator. For executables that parse their own flags (tools/): such an argument belongs to the config machinery and should be passed over rather than rejected as unknown, while an argument matching no known key under any spelling can still be flagged as a probable typo.

val read_env_var : Base.String.t -> (Base.String.t * Base.String.t) option

The environment sublevel of get_global_arg: returns the setting's value and the variable it came from. An empty value counts as unset.

val read_cmdline_or_env_var : Base.String.t -> Base.String.t Base.Option.t

The bootstrap reader: the few keys that are consulted before the config file exists (and hence before profiles are resolved) come from the commandline or the environment only.

Silent, deliberately. These keys are read before log_config_sourcing is resolved -- one of the reads IS that resolution -- so nothing here can know whether anyone asked for a trace, and each is read more than once besides (three call sites consult suppress_welcome_message). Their provenance is reported once, in full, by bootstrap_config_report below.

val filename_parts : string -> string list
val filename_of_parts : string list -> string
val log_config_sourcing_arg : Base.String.t Base.Option.t
val parse_config_lines : source:Base.String.t -> Base.String.t Base.List.t -> (Base.String.t * Base.String.t) Base.List.t

Parses the ocannl_config syntax: one key=value per line, # and ~~ lines are comments, empty values mean "unset", the ocannl_ key prefix is optional and keys are case-insensitive. Shared by the config file and by the embedded profile payloads (which are literally partial config files); source names the origin in error messages.

val config_table_of_lines : source:Base.String.t -> Base.String.t Base.List.t -> (Base.String.t, Base.String.t) Base.Hashtbl.t
val config_file_args : (Base.String.t, Base.String.t) Base.Hashtbl.t

Configuration profiles (gh-ocannl-559)

type config_level =
  1. | Cmdline_level
  2. | Env_level
  3. | Config_file_level

The source levels a setting can come from, in decreasing priority. Each level splits into two sublevels: the keys stated explicitly at that level, then the payload of a profile picked at that level -- so a specific setting always beats an aggregate one of equal immediacy, and a profile named on the commandline still overrides an exhaustive config file.

val equal_config_level : config_level -> config_level -> bool
val describe_config_level : config_level -> string
type config_source =
  1. | From_cmdline of Base.string
    (*

    the matching Sys.argv element

    *)
  2. | From_env of Base.string
    (*

    the matching environment variable

    *)
  3. | From_config_file
  4. | From_profile of config_level * Base.string
    (*

    the level that picked the profile, and its name

    *)
  5. | From_default

Where get_global_arg found a value; reported by the log_config_sourcing trace.

val config_source_label : config_source -> string

A short provenance tag, e.g. "profile 'reproducible' via the commandline".

val describe_config_source : value:string -> default:string -> config_source -> string
val resolve_config_value : cmdline:('a -> ('b * Base.string) Base.Option.t) -> env:('a -> ('b * Base.string) Base.Option.t) -> file:('a -> 'b Base.Option.t) -> profile:(config_level * Base.string * ('a -> 'b Base.Option.t)) option -> default:'b -> arg_name:'a -> 'b * config_source

The precedence walk, factored out of get_global_arg so it can be exercised on synthetic sources (see test/operations/config_profiles.ml). The lookups are pure; logging happens at the call site, which is the only place that knows which sublevel won.

val profile_ineligible_keys : (Base.String.t, Base.String.comparator_witness) Base.Set.t

The keys a profile payload may not set: they are read before profiles are resolved (or would make the resolution recursive).

val reproducible_profile_payload : string
val performance_profile_payload : string
val profile_payloads : (string * string) list

The embedded profile payloads, by name. Each is literally a partial ocannl_config file: same syntax, same parser, setting only the keys where the profiles' goals disagree.

val parse_profile_payload : name:string -> Base.String.t -> (Base.String.t, Base.String.t) Base.Hashtbl.t
val active_profile : (config_level * Base.String.t * (Base.String.t, Base.String.t) Base.Hashtbl.t) Base.Option.t

The profile picked for this run, if any: its level (which decides the priority of its payload), its name, and the parsed payload.

val profile_lookup : (config_level * Base.String.t * (Base.String.t Base.Hashtbl.key -> Base.String.t option)) Base.Option.t
val get_global_arg_with_source : default:Base.String.t -> arg_name:Base.String.t -> Base.String.t * config_source

Retrieves the arg_name setting from the commandline, the environment, the config file, or the payload of the profile picked at one of those levels; returns default if none has it, together with where the value came from.

val get_global_arg : default:Base.String.t -> arg_name:Base.String.t -> Base.String.t
val get_global_flag : default:bool -> arg_name:Base.String.t -> bool
val original_log_level : Base.Int.t
val filename_concat : Base.String.t -> Base.String.t -> Base.String.t
val clean_filename : Base.String.t -> Base.String.t
val artifacts_subdir : unit -> Base.String.t option
val ensure_artifacts_dir : Base.String.t -> Base.String.t Base.Option.t -> Base.String.t
val build_files_dir : unit -> Base.String.t

The directory generated-code debug files are written to (created if missing): build_files/<prefix>/, where the prefix defaults to the executable's base name.

val build_file : Base.String.t -> Base.String.t
val log_files_dir : unit -> Base.String.t

The directory diagnostic and routine-debug logs are written to (created if missing): log_files/<prefix>/, sharing the prefix resolution of build_files_dir.

val diagn_log_file : Base.String.t -> Base.String.t
val get_local_debug_runtime : unit -> (module Minidebug_runtime.Debug_runtime)
val _get_local_debug_runtime : unit -> (module Minidebug_runtime.Debug_runtime)
val set_log_level : Base.int -> unit
val restore_settings : unit -> unit
val with_runtime_debug : unit -> Base.bool
val debug_log_from_routines : unit -> Base.bool
val never_capture_stdout : unit -> bool
val enable_runtime_debug : unit -> unit
val get_global_run_id : unit -> int

A fresh non-negative integer per call, wrapping to 0 on overflow. Backends use it to tell one run of a kernel from the next: cc names each dynamically loaded library by it (there can be only one library of a given name in a process), and the GPU backends prefix a launch's routine logs with it. Process-wide rather than per backend, so that ids never collide across the backends a process links.

val union_find : equal:('a -> 'a -> bool) -> ('a, 'a, 'b) Base.Map.t -> key:'a -> rank:Base__Int.t -> 'a * Base__Int.t
val union_add : equal:('a -> 'a -> bool) -> ('a, 'a, 'b) Base.Map.t -> 'a -> 'a -> ('a, 'a, 'b) Base.Map.t
val unique_keep_first : equal:('a -> 'a -> bool) -> 'a list -> 'a Base.List.t

Filters the list keeping the first occurrence of each element.

val sorted_diff : compare:('a -> 'b -> int) -> 'a Base.List.t -> 'b list -> 'a Base.List.t

Returns the multiset difference of l1 and l2, where l1 and l2 must be sorted in increasing order.

val remove_elem : equal:('a -> 'b -> bool) -> 'a -> 'b list -> 'b Base.List.t

Removes the first occurrence of an element from the list that is equal to the given element.

val parallel_merge : (from:Base__Int.t -> to_:Base__Int.t -> unit) -> Base.int -> Base.unit

parallel_merge merge num_devices progressively invokes the pairwise merge callback, converging on the 0th position, with from ranging from 1 to num_devices - 1, and to_ < from.

val (!@) : 'a Stdlib.Atomic.t -> 'a
type atomic_bool = Base.bool Stdlib.Atomic.t
val sexp_of_atomic_bool : Base.bool Stdlib.Atomic.t -> Sexplib0.Sexp.t
type atomic_int = Base.int Stdlib.Atomic.t
val sexp_of_atomic_int : Base.int Stdlib.Atomic.t -> Sexplib0.Sexp.t
val sexp_append : elem:Base.Sexp.t -> Base.Sexp.t -> Base.Sexp.t
val sexp_mem : elem:Base.Sexp.t -> Base.Sexp.t -> bool
val sexp_deep_mem : elem:Base.Sexp.t -> Base.Sexp.t -> Base.bool
val split_with_seps : Re.re -> string -> string Base.List.t

Rendering a double as text (gh-ocannl-623, gh-ocannl-713)

val normalize_exponent : Base.String.t -> Base.String.t

s with any exponent's leading zeros removed, so that the rendering does not depend on which C runtime formatted it.

OCaml's %g goes through the platform's snprintf, and the Windows runtimes pad the exponent to three digits (1e+020 where glibc writes 1e+20) -- which would make generated kernels, IR dumps, and any golden quoting one, differ by platform. Both spellings denote the same number, so this is about the artifact being reproducible rather than about the value. Digits that are not padding are kept: 1e-300 stays itself.

val decimal_float_literal : Base.Float.t -> Base.String.t

The decimal spelling of the double c: a floating literal -- a radix point or an exponent is always present -- that parses back to exactly c.

Two properties %.16g alone gets wrong, and both of them matter wherever a constant is written down: in emitted kernel text (C_syntax.c_float_literal, gh-ocannl-623) and in the IR dumps (Low_level.to_doc and Low_level.to_doc_cstyle, gh-ocannl-713).

  • It is a floating literal, not an integer one. %.16g of a value with no fractional part has no radix point, so 2. comes out as "2" and -0. as "-0" -- the integer zero, hence +0.0 once read as C, and indistinguishable from +0. to a reader of a dump. That is the one distinction the gh-ocannl-615 chase was about: asking whether a -0.0 in host data survives to the kernel, and being answered := 0 either way. Forcing the radix point closes the whole class at once.
  • It round-trips. 16 significant digits do not recover every double: 0.1 +. 0.2 prints as "0.3", which is a different double, and so a constant whose 17th digit matters displays identically to one whose does not. The rendering therefore retries at %.17g -- the width IEEE-754 guarantees -- whenever the 16-digit spelling does not parse back to c. Values that already round-trip keep their exact previous spelling, which is why goldens move only by the appended .0.

Non-finite values get %.16g's words (inf, -inf, nan): they have no decimal spelling at all, and appending a radix point to one would produce a token that is neither. A caller whose dialect needs its own spelling handles them before calling -- C_syntax.c_float_literal spells C's INFINITY and NAN, while an IR dump, which is not C, keeps the words.

module Lazy : sig ... end
type requirement =
  1. | Skip
  2. | Required
  3. | Optional of {
    1. callback_if_missing : Base.unit -> Base.unit;
    }
val compare_requirement : requirement -> requirement -> Base.int
val requirement_of_sexp : Sexplib0.Sexp.t -> requirement
val sexp_of_requirement : requirement -> Sexplib0.Sexp.t
val default_indent : int Base.ref
val doc_of_sexp : Base.Sexp.t -> PPrint.document
val output_to_build_file : fname:Base.String.t -> (PPrint.ToChannel.document -> Base.unit) option
val get_debug_output_channel : fname:Base.String.t -> Stdio.Out_channel.t option
exception User_error of Base.string
val header_sep : Re.re
val log_trace_tree : 'a -> unit
module Set_O : sig ... end
val no_ints : (Base.Int.t, Base.Int.comparator_witness) Base.Set.t
val one_int : Base.Int.t -> (Base.Int.t, Base.Int.comparator_witness) Base.Set.t
val map_merge : ('a, 'b, 'c) Base.Map.t -> ('a, 'b, 'c) Base.Map.t -> f:('b -> 'b -> 'b) -> ('a, 'b, 'c) Base.Map.t
val mref_add : ('a, 'b, 'c) Base.Map.t Base.ref -> key:'a -> data:'b -> or_:('b -> Base.unit) -> Base.unit
val mref_add_missing : ('a, 'b, 'c) Base.Map.t Base.ref -> 'a -> f:(unit -> 'b) -> Base.unit
type 'a mutable_list =
  1. | Empty
  2. | Cons of {
    1. hd : 'a;
    2. mutable tl : 'a mutable_list;
    }

A mutable linked list structure.

val equal_mutable_list : 'a. ('a -> 'a -> Base.bool) -> 'a mutable_list -> 'a mutable_list -> Base.bool
val mutable_list_of_sexp : 'a. (Sexplib0.Sexp.t -> 'a) -> Sexplib0.Sexp.t -> 'a mutable_list
val sexp_of_mutable_list : 'a. ('a -> Sexplib0.Sexp.t) -> 'a mutable_list -> Sexplib0.Sexp.t
val empty : 'a mutable_list
val cons : hd:'a -> tl:'a mutable_list -> 'a mutable_list
val is_empty : 'a mutable_list -> bool
val is_cons : 'a mutable_list -> bool
val empty_val : 'a mutable_list -> unit Stdlib.Option.t
val cons_val : 'a mutable_list -> ([> `hd of 'a ] * [> `tl of 'a mutable_list ]) Stdlib.Option.t
module Variants_of_mutable_list : sig ... end
val insert : next:'a -> 'a mutable_list -> 'a mutable_list
val tl_exn : 'a mutable_list -> 'a mutable_list
type 'a weak_dynarray = 'a Stdlib.Weak.t Base.ref

A dynamic array of weak references.

val weak_create : unit -> 'a weak_dynarray
val sexp_of_weak_dynarray : ('a -> Sexplib0.Sexp.t) -> 'a Stdlib.Weak.t Base.ref -> Sexplib0.Sexp.t
val register_new : 'a weak_dynarray -> ?grow_by:??? -> (int -> 'a) -> 'a
val weak_iter : 'a weak_dynarray -> f:('a -> unit) -> unit
type 'a safe_lazy = {
  1. mutable value : [ `Callback of Base.unit -> 'a | `Value of 'a ];
  2. unique_id : Base.string;
}

A lazy value that can be safely forced and compared by unique ID.

val safe_lazy : Base.string -> (Base.unit -> 'a) -> 'a safe_lazy
val safe_force : 'a safe_lazy -> 'a
val is_safe_val : 'a safe_lazy -> bool
val safe_map : upd:Base.String.t -> f:('a -> 'b) -> 'a safe_lazy -> 'b safe_lazy
val equal_safe_lazy : ('a -> 'b -> bool) -> 'a safe_lazy -> 'b safe_lazy -> bool
val compare_safe_lazy : ('a -> 'b -> int) -> 'a safe_lazy -> 'b safe_lazy -> int
val hash_fold_safe_lazy : 'a -> Base__.Ppx_hash_lib.Std.Hash.state -> 'b safe_lazy -> Base__.Ppx_hash_lib.Std.Hash.state
val sexp_of_safe_lazy : ('a -> Base.Sexp.t) -> 'a safe_lazy -> Base.Sexp.t
module Tree_map : sig ... end

A persistent map implemented as a balanced binary tree. The sexp_of function preserves and displays the tree structure.

module Cpu_topology : sig ... end

CPU topology facts for the worker-pool uniformity policy (gh-ocannl-530, docs/proposals/gh-ocannl-530-pool-uniformity.md). Facts only — the policy that consumes them lives in Cc_backend. Every probe degrades to an "unknown" answer ([], `Unknown, a fallback count) rather than raising; the consumers treat unknown conservatively.

module Atomic_file : sig ... end

Publishing a file so that no reader ever observes a half-written one (gh-ocannl-780).

type build_file_channel = {
  1. f_path : Base.string;
  2. oc : Stdlib.out_channel;
  3. finalize : Base.unit -> Base.unit;
}
val open_build_file : base_name:Base.String.t -> extension:Base.String.t -> build_file_channel
val captured_log_prefix : string Base.ref
type captured_log_processor = {
  1. log_processor_prefix : Base.string;
  2. process_logs : Base.string Base.list -> Base.unit;
}
val captured_log_processors : captured_log_processor Base.list Base.ref
val add_log_processor : prefix:Base.string -> (Base.string Base.list -> Base.unit) -> Base.unit
val input_scan_line : Stdlib.in_channel -> Base.int
val flush_c_streams : Base.unit -> Base.unit
val input_line : Stdlib.in_channel -> bool * Base.String.t
val capture_stdout_logs : (unit -> 'a) -> 'a
val log_debug_routine_logs : log_contents:Base.string Base.List.t -> stream_name:Base.String.t -> unit
val log_debug_routine_file : log_file_name:Base.string -> stream_name:Base.String.t -> unit
val gcd : Base__Int.t -> Base__Int.t -> Base__Int.t