Low_level.Canonical_rendergh-563: the one canonical rendering of lowered code, shared by both digest consumers — analysis_cache_stats' cache (keyed inside optimize) and Schedule_cache.canonicalize (schedule replay across sessions).
The walk is the same for both: index / scalar / statement emission, loop-binder tokens, local-scope alpha renaming, comment skipping, opaque-statement handling. What deliberately differs is the identity policy — the analysis cache keys tensor nodes and static symbols by identity (a hit reuses the stored code verbatim), the schedule cache alpha-renames everything (a hit replays a schedule onto a different-but-isomorphic lowering).
Both digests are correctness-critical, so keep the split honest: a new t / scalar_t construct is rendered in the walk and only there (the matches are exhaustive, so omitting it breaks the build); a new digest-relevant fact enters the walk if it belongs to the code itself, or exactly one policy field / one consumer preamble if it is an identity choice or a consumer-specific companion.
type policy = {emit_tn : Tnode.t -> Base.unit;Render a tensor node reference.
*)emit_free_sym : Indexing.symbol -> Base.unit;on_bind_loop : Indexing.symbol ->
id:Base.int ->
shadowed:Base.bool ->
Base.unit;Called when a For_loop binder mints the token "b<id>". shadowed iff the symbol already had a token: a duplicated binder makes symbol references ambiguous.
mark_incomplete : Base.unit -> Base.unit;Called when an opaque construct makes the rendering an unfaithful summary of the code.
*)mma : mma_policy;initial_tokens : (Indexing.symbol * Base.string) Base.list;Symbols pre-bound to a rendering token before the walk — the static indices, for the consumer that renders them positionally.
*)}