Lecture, Tutorial
Inferring Locality in OCaml | OCaml Unboxed
- The OCaml compiler employs inference to determine whether local variables and parameters should be treated as "global" or "local," resolving ambiguities that arise when values do not explicitly escape or are not annotated.
- Inferred local parameters are preferred because "local" is a supertype of "global," allowing a function accepting local arguments to also accept global ones, thereby maximizing permissiveness.
- Inferred return values generally default to "global" to avoid imposing escaping constraints on the caller, which would otherwise prevent usage in contexts requiring global values (e.g., standard library I/O).
- The compiler uses an internal
alloc mode(e.g.,mode var,global,local) which can be inspected via the-dtypedtreeflag to debug type inference decisions, though this output is intended for compiler debugging rather than routine use. - When no signature (
.mlifile) exists for a module, the compiler performs a defaulting step that removes alllocalunderscores from inferred types to prevent strange behaviors in projects lacking explicit signatures. - Inference allows a function with an inferred local return type to be assigned to a variable expecting a global return type, but not vice versa; adding a
localrequirement to a return type imposes stricter constraints on the caller that the implementation cannot retroactively satisfy. - Integers (
int) represent a significant exception to the locality system because they are "immediates" (stored without pointers) and can "mode cross," meaning a localintcan be safely treated as a global value even if stored in a reference or passed to a function. - To utilize the "mode crossing" feature for integers, an explicit type annotation is required; relying solely on inference to deduce that a value is an
intis currently insufficient to trigger the safety check. - The community is developing improvements to provide finer-grained control over what types can "mode cross," potentially allowing safety constraints on non-pointer types beyond just
int. - Function types can be converted if the conversion adds requirements on the caller (e.g., a
globalreturn value can be treated aslocalif the caller promises not to let it escape) but cannot remove requirements from the implementation (e.g., a function must explicitly promise not to let a parameter escape before being used in a context requiring that promise). - Workarounds such as function expansion (e.g.,
fun x -> h x) are sometimes necessary to bypass rejection of type conversions where the implementation implicitly satisfies a requirement that the type signature explicitly demands. - The "local" allocation discipline was introduced at Jane Street to enable stack-based allocation for certain data, reducing heap usage and garbage collection overhead compared to the default heap allocation.
- Current standard library functions often lack awareness of the
localmode, frequently forcing values to be treated as global during operations, which complicates demonstration and usage of locality in everyday code. - Explicit
localannotations are not required for variables that do not escape, as the compiler's inference engine generally defaults to the most efficient local allocation when possible.