1:- module(hash_consing,
    2          [ intern/2,               % ?Term, ?Id
    3            represented/2,          % ?Layer, ?Representation
    4            is_id/1,                % +Value
    5            externalized/2,         % +TermWithIds, -External
    6            internalized/3,         % +Templates, +External, -TermWithIds
    7            declared/1,             % +Templates
    8            rewritten/1,            % +Templates
    9            rewritten/2,            % +Templates, +Options
   10            store_property/1,       % ?Property
   11            store_growth_bounded/1  % +Bytes
   12          ]).

hash_consing: hash-consed terms selected by templates, whose Ids record a shape, and a term rewrite that makes a program use them

A GENERIC LIBRARY: it knows no constructor of any calculus and keeps no configuration but the templates that files and declared/1 declare.

HOW TO USE IT. A file is written as the program that does not intern, loads this library and names, by templates, the terms to intern:

:- module(steps, [step/2, built/2]).
:- use_module(library(hash_consing), []).
:- hash_consing:rewritten([apply(lambda(_), _), lambda(_), variable(_)]).

step(apply(lambda(Body), Argument), beta(Body, Argument)).

built(Function, Applied) :-
    Applied = apply(Function, variable(0)).

A term is interned when a template of its constructor matches it (TEMPLATES ARE PATTERNS, below); here an application is interned when its first argument is a lambda, and any other application stays a plain compound whose arguments are represented in turn. Files that hand such terms to each other declare the same templates, most simply by including one file that holds the directive. Code that did not go through the rewrite crosses the boundary explicitly:

?- hash_consing:internalized([apply(lambda(_), _), lambda(_), variable(_)],
                             apply(lambda(variable(0)), variable(1)), Id),
   steps:step(Id, Result),
   hash_consing:externalized(Result, External).
Id = '__hash_consed_apply/2'('lambda/1', <handle>),
External = beta(variable(0), variable(1)).

With the list [] the file loads unchanged, which is the program without interning. The test test/hash_consing.plt loads the fixtures in `test/hash_consing_fixtures/` and compares what the rewrite makes of them with the snapshots in `test/hash_consing_snapshots/`, the place to read exact rewritten clauses.

`intern(?Term, ?Id)` IS ONE RELATION WITH TWO DIRECTIONS (user, 2026-09-17: a binary predicate that upserts forwards and looks up backwards). Term is a ground term whose subterms are represented already. Called with Id bound it looks the term up (and with Term bound as well it is a check that inserts nothing); called with Id unbound, or bound to an Id pattern whose Handle is unbound, it inserts Term unless it is there and answers its Id. It fails, inserting nothing, when no template of Term's constructor matches Term (user, 2026-09-26: a term that matches no template is not interned, even when its constructor has templates), and raises existence_error(templates, Name/Arity) when the constructor has no template in this process, which is most likely a missing declaration (user, 2026-09-26, approving the change). A value that is no Id matches nothing and the call fails, as a pattern that does not match fails without interning (user ruling, 2026-09-17: an uninterned instance at an Id position fails to unify rather than raising). The store is canonical (the same term, the same Id), idempotent and monotone (it only grows and backtracking does not shrink it), so an answer once given is never contradicted. An Id is abstract: only ==, intern/2, represented/2, is_id/1 and the two boundary predicates look at it.

AN ID IS '__hash_consed_Name/Arity'(Handle) OR '__hash_consed_Name/Arity'(Shape, Handle). Name/Arity is the root constructor of the term and Handle the node handle of one trie, the store. The root is in the functor name so that the rewrite turns a head pattern apply(F, X) into an Id pattern that first argument indexing tells apart by constructor, and a value of another constructor, or no Id at all, fails at head unification without a lookup (probe, 2026-09-17: with one functor for every Id, 156 ms against 0.9 ms for 1000 calls over 2000 clauses). The prefix follows SWI-Prolog's convention for names a library synthesises (library(apply_macros)'s '__aux_maplist/N_...'); a leading $ is reserved for the system.

THE SHAPE is there for clauses told apart by an INNER constructor,

step(apply(lambda(Body), Argument)) :- ...
step(apply(closure(Capture), Argument)) :- ...

whose heads would otherwise be one Id pattern, every candidate clause paying a lookup before it fails. Shape is ONE ATOM that spells the constructors found below the root, as far as the term's template says (user, 2026-09-20: the Id keeps one layer, the shape may have several; the layers are joined into one atom, not kept as several shapes; how deep is configurable). Under the templates apply(lambda(_), _) and apply(closure(_), _) the heads above become step('__hash_consed_apply/2'('lambda/1', Handle)) and step('__hash_consed_apply/2'('closure/1', Handle)), and the deep index SWI-Prolog builds on the first argument of the first argument tells them apart without a lookup (benchmark, 2026-09-20: 1000 calls over 2000 such clauses, 621 ms without the shape and 3 ms with it; upserts 46 percent slower; the store no larger).

THE ID STILL HAS ONE LAYER. The shape is not in the functor name, because a shallow pattern apply(F, X) has to match the Ids of every shape and the set of inner constructors is open across files; and the Id holds no Id of a subterm, because a truncation would give one term two spellings and split == and the table keys. A shape is a function of the content and of the constructor's templates, so the Handle determines it and the store stays canonical.

TEMPLATES ARE PATTERNS, in one syntax for every constructor (user, 2026-09-20: one syntax, not three; user, 2026-09-26: hash_consing is based on patterns, not on constructors). An element of a list of templates is a constructor applied to one of these at each argument position (a constructor of arity zero is its atom):

_                  any argument; nothing is recorded
*                  any argument; its root constructor is recorded
a nested template  an argument whose root constructor is the template's
                   and whose arguments match the template's arguments;
                   its root is recorded, and what the template records
                   below it

* records the root constructor of the term the argument stands for, Name/Arity, whether that term is interned or not, and - for a number or a string. There is no alternative inside a template, and ; is an ordinary constructor: the alternatives of a constructor are several templates in the list, because Prolog's ; is a disjunction whose two sides can both hold, and templates are not predicates (user, 2026-09-26).

A CONSTRUCTOR'S TEMPLATES ARE TRIED IN ORDER, the order of the list, and the first that matches a term is the term's template; the templates after it are not looked at, and a term has one template or none (user, 2026-09-26: the parts where templates overlap use the first matching template; matching several templates at once has no meaning). The shape is what that template records, spelled as the recorded descriptions joined by commas, '' when it records nothing. Under apply(apply(*, _), _) then apply(*, _), the term apply(apply(lambda(B), Y), X) has the shape 'apply/2(lambda/1)' and apply(lambda(B), X) the shape 'lambda/1'; under apply(apply(*, _), _) alone the second is not interned. A template that one earlier template always takes first raises while it is declared, so a constructor whose templates record nothing has the one template `Name(_, ..., _)`, and its Ids have no shape; the Ids of any other constructor have one. Two templates that record different positions can spell the same atom for different terms, which only keeps the index from telling their clauses apart.

MATCHING AND RECORDING READ THE TERM, not how its subterms are represented: an argument that is an Id is read through the store and an argument that is a plain compound as it is, and both give the same root and arguments. At a * the root of an Id is read off its functor name, with no lookup; a nested template costs one trie_term/2 on an argument that is an Id. The spelling of a shape is remembered in a trie keyed by its structure, so an atom is built once per structure; the atoms are finitely many, bounded by the templates and the constructors.

ONE LIST OF TEMPLATES PER CONSTRUCTOR, PER PROCESS: the first declaration of a constructor, by rewritten/1, declared/1 or internalized/3, fixes its list, and another declaration with other templates or another order raises permission_error(reconfigure, interned_constructor, Name/Arity), because whether a term is interned and what its shape is must agree wherever it is handed (agent design decision, 2026-09-20, for one template, and 2026-09-26 for a list, in plans the user approved; between files the library checks nothing else, by the user's ruling of 2026-09-17: which files list which constructors is the user's to arrange, for instance with one included file). A constructor is never declared by being met.

THE STORE IS ONE TRIE, used through SWI-Prolog's trie API directly (user, 2026-09-17, after a benchmark of four candidate stores: "use the trie"). Its key is the term itself, constructor included (a trie shares a functor node among all its keys), and its value is the key's own handle, written back with trie_update/3 right after the insertion, because inserting an existing key with a different value raises instead of failing. It lives outside the table space and trie_property(Trie, value_count(Count)) counts it.

A TERM THAT IS NOT INTERNED STAYS AS IT IS (user, 2026-09-26): a plain compound of its constructor, whose arguments are represented in turn. The REPRESENTATION of a term is its Id when a template of its constructor matches it, and the term itself, with represented arguments, otherwise.

THE BOUNDARY: externalized/2 replaces every Id by its term, recursively, for printing, for writing a file and for a content address. internalized/3 declares the templates it is given and represents, bottom up, the instances of their constructors, for a term that did not come through the rewrite (a toplevel query, read_term/2, a module that did not opt in, a term built by =..). declared/1 only declares templates, for a program whose constructors are made at run time. is_id/1 tells an Id from any other value. `represented(?Layer, ?Representation)` relates one layer, a constructor applied to represented arguments, to its representation.

A LAYER POSITION holds a layer, not a representation: in a file that opts in, the term written there keeps its own constructor as a plain term and only its arguments are rewritten. The first argument of represented/2 is one; rewritten/2's option layer_arguments names more, in the heads and goals of the file, for a predicate of a module that does not opt in and hands out or takes the plain layer of a listed constructor (a reader of one layer). A variable there stays a variable.

:- hash_consing:rewritten(Templates). OPTS A FILE IN (user ruling, 2026-09-17: opt-in per file, the constructors passed to the directive, no setting). It declares the templates as declared/1 does, and every clause read after it in that file, included files counted, has each occurrence of a constructor of the list rewritten as below, the direction of each call decided at run time, so the file is written as the program that does not intern and needs no mode declaration. Several directives in one file add up, each declaring whole lists. An empty list rewrites nothing. The library checks no consistency between files: files that pass one constructor to each other must both list it, which the user arranges (user ruling, 2026-09-17). SHARE ONE DIRECTIVE BY include/1: keep the directive in one file that every file handing these terms to another includes; an included directive opts in the file that includes it. A file left out fails silently (the FIXME below).

AN OCCURRENCE IS CLASSIFIED while the file loads, by comparing its source pattern with each template of its constructor: every instance of the pattern matches the template, none does, or some do.

must intern       a template matches every instance: an Id pattern and
                  a call of `intern/2`; the shape is written in when the
                  first template that some instance matches matches them
                  all and each root it records is in the pattern
must not intern   no template matches any instance: the layer itself,
                  written where the occurrence stood
undetermined      otherwise: a fresh variable where the occurrence
                  stood, and a call of `represented/2`

A pattern whose variables are all filled with an atom no template names is taken by a template only when that template has _ or * there, and such a template takes every instance, so no occurrence is missed as must intern. The other two tests treat each occurrence of a variable apart, so a pattern with a repeated variable can be classified undetermined, or left without its shape, although its instances decide it; that is correct and unindexed. An undetermined occurrence at the top of a head costs its clause the first argument indexing of that argument: the clause is a candidate for every call, so a call that a clause before it answers leaves a choice point on it (measured 2026-09-26: with such a clause first in its predicate, calls on an Id, a plain term and the other constructors' Ids were deterministic; with it last, each of them left a choice point). Such a clause is not split into an Id version and a plain version: called with that argument unbound, both would run the body, and a cut in the first would prune the second.

THE REWRITE OF A CLAUSE:

head       fast path when every top-level occurrence is given (the
           handle of an Id pattern bound; the variable of an undetermined
           occurrence bound, and not to an Id pattern whose handle is
           unbound): relate them to their layers top down, then run the
           body, which keeps the last call; otherwise relate the given
           ones top down, represent the ground ones bottom up, run the
           body, represent the rest bottom up (an instantiation error
           when a term that must be interned is still not ground, or an
           undetermined one is still undecided)
body goal  when the variables of its layers are bound: represent bottom
           up and call; otherwise intern the ground ones and decide each
           undetermined one (its Id, an Id pattern whose handle is
           unbound, or the layer itself; an instantiation error when
           still undecided), call, and then settle every occurrence top
           down (a lookup when the call bound the Id, an insertion when
           it made the term ground, an instantiation error when
           neither). An Id occurrence over variables that occur once in
           the clause is unread: its Id pattern alone is passed, and
           after the call only its handle is checked
ground     an occurrence that is ground in the source is represented
           while the file loads, and its Id or its layer is written into
           the clause

Control constructs (`,`, ;, ->, *->, \+, call/1, forall/2, the goal of findall/3 and of catch/3, a module-qualified goal) are rewritten inside. A listed constructor in the template or the result of findall/3, in the catcher of catch/3, or in a DCG rule raises while the file loads. Directives are not rewritten.

TWO RULES FOR A FILE THAT OPTS IN. (1) An instance of a listed constructor whose interning is undetermined is decided when it is handed to a goal: what is bound of it settles whether a template matches every completion or none does, or the call raises an instantiation error; and every instance is settled, ground or its Id bound, by the end of the goal it is handed to. The first half is a DOWNGRADE of the rule before 0.2.0, which asked only for the second: a term that matches no template stays as it is (user, 2026-09-26), and a callee could not otherwise tell whether to receive an Id or a plain term. (2) Reflection sees Ids: =.., functor/3, arg/3, write/1, variant_sha1/2, term_hash/2 and ordering whose result depends on the order see '__hash_consed_Name/Arity'(Handle); the standard order of two Ids depends on the history of insertions into the store, not on their terms. Code that reads structure calls externalized/2 first, and code that builds an instance with them calls internalized/3 after; an instance that must be interned and is left uninterned fails to unify at every rewritten position. Using an Id as an opaque key, whose result does not depend on the order (an assoc key, a sort to remove duplicates), is fine.

THE STORE BELONGS TO THE PROCESS. The store and the spellings of the shapes are two tries made when this module is first loaded and kept in the fact process_stores/2, so every thread and every engine interns into the same store and reads the same Ids, with nothing to install. Reloading the module keeps them. A lookup that finds its key with its handle written back takes no lock; every write to the store, a value still pending settled included, is made under a mutex. The Prolog flag hash_consing_store_limit, infinite by default, bounds the bytes of the store: an insertion that would grow it past the limit raises resource_error(hash_consing_store), the way the stack and table limits do, and the store stays as it was. store_growth_bounded/1 bounds instead what one thread adds to the store from the call on, the way the stack and table limits bound one thread, so a thread that shares the store with others, or finds it already full of what earlier work interned, is charged with its own insertions only. A declaration, an insertion into the store and a new spelling each take a mutex (hash_consing_templates, hash_consing_store, hash_consing_spellings) and look again under it before they write. store_property/1 reads the size of the store.

AN ID IS NEVER FREED NOR REUSED. The store only grows: an Id stays valid, and stands for the same term, until the process ends. Two Ids are == exactly when the terms they stand for are ==, so an Id is a key of its term.

AN INTERNED TERM IS A VALUE, NOT A CELL. intern/2 stores a copy of the term as it is at the call: a later setarg/3 or nb_setarg/3 on the term that was interned, or on a layer read back from an Id, changes that copy only, never the store, and interning the old value again answers the same Id. So a mutable cell (a thunk, a memory cell updated in place) must stay outside the store; interning it freezes its value at that moment. An Id itself must never be mutated: an Id whose handle is set to another Id's handle is that other Id, and one set to any other integer crashes the process (the FIXME below on trie_term/2).

THIS MODULE REFLECTS ON SOURCE CLAUSES with =.. while a file loads, a mechanical boundary; nothing here chooses behaviour at run time by reflection, and no closure is handed to another module.

FIXME: trie_term/2 on an integer that is no node of the store crashes the process with a segmentation fault (measured 2026-09-14 on trie_term(42, _)); a forged '__hash_consed_apply/2'(42) reaches it. Only a bug makes such a value. Candidate fix: none in the trie API; the assertz backends of the benchmark raise instead, at the costs recorded there.

FIXME: an Id written into a clause while the file loads is a handle of this process. A file must be loaded from source, never qcompiled nor saved in a state.

FIXME: a file that uses a constructor another file interns, without listing it, passes its instances uninterned; the rewritten positions of the other file then fail to unify, silently. The library does not check it; one directive shared by include/1 (above) avoids it.

  341:- use_module(library(error), [must_be/2]).  342:- use_module(library(lists), [reverse/2, member/2]).  343
  344:- initialization(stores_created).
 stores_created
The store and the spellings of the shapes, two tries of the process, made unless an earlier load of this module made them.
  351:- dynamic process_stores/2.  352
  353stores_created :-
  354    (   process_stores(_, _)
  355    ->  true
  356    ;   trie_new(Store),
  357        trie_new(Spellings),
  358        assertz(process_stores(Store, Spellings))
  359    ).
 store_property(?Property) is nondet
A property of the store: ids(Count), the number of terms interned; nodes(Count), the number of nodes of its trie; bytes(Bytes), the memory the trie takes. The spellings of the shapes are not counted.
  367store_property(ids(Count)) :-
  368    process_stores(Store, _),
  369    trie_property(Store, value_count(Count)).
  370store_property(nodes(Count)) :-
  371    process_stores(Store, _),
  372    trie_property(Store, node_count(Count)).
  373store_property(bytes(Bytes)) :-
  374    process_stores(Store, _),
  375    trie_property(Store, size(Bytes)).
  376
  377%   ---- the relation ----
 intern(?Term, ?Id)
Id is the Id of the ground term Term.
  383intern(Term, Id) :-
  384    nonvar(Id),
  385    !,
  386    id_parts(Id, _, _, Handle),
  387    (   nonvar(Handle)
  388    ->  trie_term(Handle, Stored),
  389        Term = Stored
  390    ;   upserted(Term, Id)
  391    ).
  392intern(Term, Id) :-
  393    upserted(Term, Id).
 upserted(+Term, ?Id)
When a template of Term's constructor matches it, insert Term unless it is stored, and answer its Id; fail, inserting nothing, when none does.
  400upserted(Term, Id) :-
  401    process_stores(Trie, _),
  402    trie_lookup(Trie, Term, Handle),
  403    Handle \== pending,
  404    !,
  405    stored_id(Term, Handle, Id).
  406upserted(Term, Id) :-
  407    (   ground(Term)
  408    ->  true
  409    ;   throw(error(instantiation_error, context(hash_consing:intern/2, Term-Id)))
  410    ),
  411    functor(Term, Name, Arity),
  412    templates_known(Name, Arity, hash_consing:intern/2, Templates, Kind),
  413    (   Kind == root_only
  414    ->  true
  415    ;   first_template_recorded(Templates, Term, Structure)
  416    ),
  417    process_stores(Trie, _),
  418    (   trie_lookup(Trie, Term, Found),
  419        Found \== pending
  420    ->  Handle = Found
  421    ;   with_mutex(hash_consing_store, handle_inserted(Trie, Term, Handle))
  422    ),
  423    id_built(Name, Arity, Kind, Structure, Handle, Id).
  424
  425%   stored_id(+Term, +Handle, ?Id): the Id of a term found in the store
  426%   with its handle written back. A stored term is ground and was declared,
  427%   and the first template that matched it when it was inserted still does,
  428%   so only the shape is computed again.
  429
  430stored_id(Term, Handle, Id) :-
  431    functor(Term, Name, Arity),
  432    constructor_templates(Name, Arity, Templates, Kind),
  433    (   Kind == root_only
  434    ->  known_id_name(Name, Arity, IdName),
  435        compound_name_arguments(Id, IdName, [Handle])
  436    ;   first_template_recorded(Templates, Term, Structure),
  437        id_built(Name, Arity, Kind, Structure, Handle, Id)
  438    ).
  439
  440%   handle_inserted(+Trie, +Term, -Handle): Term looked up again and, when it
  441%   is still absent, inserted, under the mutex `hash_consing_store`. Two
  442%   threads that share the store and both miss Term would otherwise both
  443%   insert it, and the second trie_insert/4 raises
  444%   `permission_error(modify, trie_key, Term)` (measured 2026-09-30: eight
  445%   threads interning the same 20000 terms raised in 5 of 5 runs). A lookup
  446%   that finds Term with its handle written back takes no mutex; a value
  447%   still `pending`, written by an insertion in progress in another thread or
  448%   left by an interrupted one, is settled here, under the mutex, so that only
  449%   the thread holding it writes to the store.
  450
  451handle_inserted(Trie, Term, Handle) :-
  452    (   trie_lookup(Trie, Term, Found)
  453    ->  settled_handle(Trie, Term, Found, Handle)
  454    ;   store_within_limit(Trie),
  455        thread_growth_within_budget(Trie),
  456        trie_property(Trie, node_count(NodesBefore)),
  457        sig_atomic(inserted(Trie, Term, Handle)),
  458        thread_growth_counted(Trie, NodesBefore)
  459    ).
 store_within_limit(+Trie)
Raises resource_error(hash_consing_store) when the Prolog flag hash_consing_store_limit is a number of bytes and the store takes more. Measuring the bytes walks the whole trie (20 ms at 200,000 terms, measured 2026-10-01), its node count does not, so the bytes are estimated from the node count and the bytes per node of the last measurement, and measured again only when the estimate passes the limit or the nodes have doubled since; only a measurement raises.
  471:- create_prolog_flag(hash_consing_store_limit, infinite, [type(term), keep(true)]).  472
  473store_within_limit(Trie) :-
  474    current_prolog_flag(hash_consing_store_limit, Limit),
  475    (   Limit == infinite
  476    ->  true
  477    ;   must_be(positive_integer, Limit),
  478        trie_property(Trie, node_count(Nodes)),
  479        bytes_per_node(Trie, Nodes, MeasuredNodes, MeasuredBytes),
  480        (   Nodes * MeasuredBytes =< Limit * MeasuredNodes
  481        ->  true
  482        ;   trie_property(Trie, size(Bytes)),
  483            flag(hash_consing_measured_nodes, _, Nodes),
  484            flag(hash_consing_measured_bytes, _, Bytes),
  485            (   Bytes > Limit
  486            ->  format(atom(Measured), 'the store takes ~D bytes, the limit is ~D', [Bytes, Limit]),
  487                throw(error(resource_error(hash_consing_store),
  488                            context(hash_consing:intern/2, Measured)))
  489            ;   true
  490            )
  491        )
  492    ).
  493
  494%   bytes_per_node(+Trie, +Nodes, -MeasuredNodes, -MeasuredBytes): the nodes
  495%   and bytes of the last measurement of the store, measured again when
  496%   there is none or the nodes have doubled since; their ratio estimates the
  497%   bytes of a node.
  498
  499bytes_per_node(Trie, Nodes, MeasuredNodes, MeasuredBytes) :-
  500    flag(hash_consing_measured_nodes, KnownNodes, KnownNodes),
  501    flag(hash_consing_measured_bytes, KnownBytes, KnownBytes),
  502    (   KnownNodes > 0,
  503        Nodes < 2 * KnownNodes
  504    ->  MeasuredNodes = KnownNodes,
  505        MeasuredBytes = KnownBytes
  506    ;   trie_property(Trie, size(Bytes)),
  507        MeasuredNodes is max(Nodes, 1),
  508        MeasuredBytes = Bytes,
  509        flag(hash_consing_measured_nodes, _, MeasuredNodes),
  510        flag(hash_consing_measured_bytes, _, MeasuredBytes)
  511    ).
 store_growth_bounded(+Bytes)
From now on, this thread may grow the store by Bytes at most; infinite lifts the bound. Once the nodes this thread inserted since the call take more than Bytes, its next insertion raises error(resource_error(hash_consing_store), context(hash_consing:intern/2, Message)), Message stating the bytes added and the budget, the error the flag hash_consing_store_limit raises. A term already in the store costs nothing: interning it again adds no node, is not counted, and is answered even past the budget. Only this thread's own insertions count, measured as the nodes each one adds under the store's mutex, so threads that share the store do not charge each other, and what the store held before the call does not count. A thread created later inherits the bound with a count of its own from zero, as it inherits stack_limit and table_space. Bytes are estimated from the nodes as for the flag hash_consing_store_limit. The bound lives in the Prolog flag hash_consing_growth_budget.
  531:- create_prolog_flag(hash_consing_growth_budget, infinite, [type(term), keep(true)]).  532
  533store_growth_bounded(Bytes) :-
  534    (   Bytes == infinite
  535    ->  true
  536    ;   must_be(positive_integer, Bytes)
  537    ),
  538    set_prolog_flag(hash_consing_growth_budget, Bytes),
  539    nb_setval(hash_consing_growth_added, 0).
  540
  541%   thread_growth_within_budget(+Trie): the nodes this thread inserted since
  542%   its budget was set take no more than the budget.
  543
  544thread_growth_within_budget(Trie) :-
  545    current_prolog_flag(hash_consing_growth_budget, Bytes),
  546    (   Bytes == infinite
  547    ->  true
  548    ;   thread_growth_added(Added),
  549        trie_property(Trie, node_count(Nodes)),
  550        bytes_per_node(Trie, Nodes, MeasuredNodes, MeasuredBytes),
  551        (   Added * MeasuredBytes =< Bytes * MeasuredNodes
  552        ->  true
  553        ;   Spent is Added * MeasuredBytes // MeasuredNodes,
  554            format(atom(Measured),
  555                   'this thread grew the store by about ~D bytes, its budget is ~D',
  556                   [Spent, Bytes]),
  557            throw(error(resource_error(hash_consing_store),
  558                        context(hash_consing:intern/2, Measured)))
  559        )
  560    ).
  561
  562%   thread_growth_added(-Added): the nodes this thread inserted since its
  563%   budget was set; zero in a thread that inherited the budget and has not
  564%   inserted yet.
  565
  566thread_growth_added(Added) :-
  567    (   nb_current(hash_consing_growth_added, Added)
  568    ->  true
  569    ;   Added = 0
  570    ).
  571
  572%   thread_growth_counted(+Trie, +NodesBefore): the nodes the insertion just
  573%   made, the store's nodes now less NodesBefore, added to this thread's
  574%   count when it has a budget. Called under the store's mutex, right after
  575%   the insertion, so no other thread's nodes are counted.
  576
  577thread_growth_counted(Trie, NodesBefore) :-
  578    current_prolog_flag(hash_consing_growth_budget, Bytes),
  579    (   Bytes == infinite
  580    ->  true
  581    ;   thread_growth_added(AddedBefore),
  582        trie_property(Trie, node_count(Nodes)),
  583        Added is AddedBefore + Nodes - NodesBefore,
  584        nb_setval(hash_consing_growth_added, Added)
  585    ).
  586
  587%   inserted(+Trie, +Term, -Handle): the insertion and the write-back of the
  588%   key's own handle as its value, run under `sig_atomic/1` by
  589%   handle_inserted/3, so
  590%   that a signal (a `call_with_time_limit/2` expiring) waits until both are
  591%   done. `call_with_inference_limit/3` is no signal: its limit can still
  592%   fall between the two and leave the value `pending` (measured 2026-09-27:
  593%   interning a fresh two-argument term under a limit of 9 inferences left it
  594%   `pending`, and the next lookup handed `pending` to `trie_term/2`, which
  595%   raised `type_error(address, pending)`). settled_handle/4 repairs such a
  596%   key at its next lookup.
  597
  598inserted(Trie, Term, Handle) :-
  599    trie_insert(Trie, Term, pending, Handle),
  600    trie_update(Trie, Term, Handle).
  601
  602%   settled_handle(+Trie, +Term, +Found, -Handle): the handle of Term's node,
  603%   Found when the value was written back, and otherwise, when an
  604%   interrupted insertion left `pending`, the node's own handle, read with
  605%   `'$trie_gen_node'/3` (SWI-Prolog's generator of a trie's keys with their
  606%   nodes, here with the key bound) and written back. The repair may itself
  607%   be interrupted between its two steps; the key then stays `pending` and
  608%   is repaired at its next lookup.
  609
  610settled_handle(Trie, Term, pending, Handle) :-
  611    !,
  612    '$trie_gen_node'(Trie, Term, Handle),
  613    trie_update(Trie, Term, Handle).
  614settled_handle(_, _, Handle, Handle).
 represented(?Layer, ?Representation)
Representation represents Layer, a constructor applied to represented arguments: its Id when a template of the constructor matches it, Layer itself otherwise.

Given an Id of the constructor, Layer is its stored term, or, when the handle is unbound, Layer is interned into it and must be ground. Given a plain term of the constructor, it is Layer, and no template may match any completion of it: it is then not the representation of a term that must be interned. Given nothing, Layer is represented as far as what is bound of it decides: its Id when it is ground and a template matches it, an Id pattern whose handle is unbound when every completion must be interned, Layer itself when none may be, and an instantiation error when what is bound does not decide. Any other value fails.

  632represented(Layer, Representation) :-
  633    (   var(Layer)
  634    ->  throw(error(instantiation_error,
  635                    context(hash_consing:represented/2, Layer-Representation)))
  636    ;   true
  637    ),
  638    functor(Layer, Name, Arity),
  639    templates_known(Name, Arity, hash_consing:represented/2, Templates, Kind),
  640    id_name(Name, Arity, IdName),
  641    (   var(Representation)
  642    ->  representation_made(Templates, Kind, Name, Arity, Layer, Representation)
  643    ;   id_parts(Representation, IdName, _, Handle)
  644    ->  (   nonvar(Handle)
  645        ->  trie_term(Handle, Stored),
  646            Layer = Stored
  647        ;   ground(Layer)
  648        ->  upserted(Layer, Representation)
  649        ;   throw(error(instantiation_error, context(hash_consing:represented/2, Layer)))
  650        )
  651    ;   functor(Representation, Name, Arity)
  652    ->  Layer = Representation,
  653        value_classified(Templates, Layer, Class, _),
  654        Class == must_not_intern
  655    ).
  656
  657representation_made(_, _, _, _, Layer, Representation) :-
  658    ground(Layer),
  659    !,
  660    (   upserted(Layer, Id)
  661    ->  Representation = Id
  662    ;   Representation = Layer
  663    ).
  664representation_made(Templates, Kind, Name, Arity, Layer, Representation) :-
  665    value_classified(Templates, Layer, Class, Structure),
  666    (   Class == must_intern
  667    ->  (   ground(Layer)
  668        ->  upserted(Layer, Representation)
  669        ;   id_built(Name, Arity, Kind, Structure, _, Representation)
  670        )
  671    ;   Class == must_not_intern
  672    ->  Representation = Layer
  673    ;   throw(error(instantiation_error, context(hash_consing:represented/2, Layer)))
  674    ).
 represented_settled(+Layer, ?Representation)
Represented/2 at the end of a rewritten head, where rule (1) of the module comment leaves no Id pattern with an unbound handle: such a pattern raises an instantiation error.
  682represented_settled(Layer, Representation) :-
  683    represented(Layer, Representation),
  684    (   id_parts(Representation, _, _, Handle),
  685        var(Handle)
  686    ->  throw(error(instantiation_error, context(hash_consing:represented/2, Layer)))
  687    ;   true
  688    ).
 is_id(+Value)
Value is an Id: an Id functor this process made, applied to a bound handle. The handle is the last argument of both Id arities, so the test reads it directly instead of through id_parts/4: lambda_jit's term_weight.pl calls it once per node it weighs.
  697is_id(Value) :-
  698    compound(Value),
  699    compound_name_arity(Value, IdName, Arity),
  700    id_constructor(IdName, _, _),
  701    arg(Arity, Value, Handle),
  702    nonvar(Handle).
 id_parts(+Value, -IdName, -Shape, -Handle)
Value is an Id or an Id pattern: its functor is one id_name/3 made, which the first argument index of id_constructor/3 finds without scanning the name (the prefix test it replaces took 30 percent of a lambda_jit read, 2026-09-26). Shape is none for a constructor without a shape. Fails on any other value.
  712id_parts(Value, IdName, Shape, Handle) :-
  713    compound(Value),
  714    compound_name_arity(Value, IdName, Arity),
  715    id_constructor(IdName, _, _),
  716    id_arguments(Arity, Value, Shape, Handle).
  717
  718id_arguments(1, Value, none, Handle) :-
  719    arg(1, Value, Handle).
  720id_arguments(2, Value, Shape, Handle) :-
  721    arg(1, Value, Shape),
  722    arg(2, Value, Handle).
 id_built(+Name, +Arity, +Kind, +Structure, ?Handle, -Id)
The Id, or the Id pattern, of the constructor with the handle Handle: no shape when Kind is root_only, an unbound shape when Structure is unknown, and otherwise the spelling of Structure.
  730id_built(Name, Arity, Kind, Structure, Handle, Id) :-
  731    id_name(Name, Arity, IdName),
  732    (   Kind == root_only
  733    ->  compound_name_arguments(Id, IdName, [Handle])
  734    ;   Structure == unknown
  735    ->  compound_name_arguments(Id, IdName, [_, Handle])
  736    ;   structure_spelled(Structure, Shape),
  737        compound_name_arguments(Id, IdName, [Shape, Handle])
  738    ).
 id_name(+Name, +Arity, -IdName)
The functor name of the Ids of the constructor Name/Arity, remembered once made, in both directions.
  745:- dynamic known_id_name/3.             % known_id_name(Name, Arity, IdName)
  746:- dynamic id_constructor/3.            % id_constructor(IdName, Name, Arity)
  747
  748id_name(Name, Arity, IdName) :-
  749    (   known_id_name(Name, Arity, Known)
  750    ->  IdName = Known
  751    ;   with_mutex(hash_consing_templates, id_name_made(Name, Arity, IdName))
  752    ).
  753
  754%   id_name_made(+Name, +Arity, -IdName): the name looked up again under the
  755%   mutex `hash_consing_templates`, and made when it is still unknown.
  756%   id_constructor/3 is asserted before known_id_name/3, so a thread that
  757%   finds the name without the mutex also finds the fact id_parts/4 reads.
  758
  759id_name_made(Name, Arity, IdName) :-
  760    (   known_id_name(Name, Arity, Known)
  761    ->  IdName = Known
  762    ;   format(atom(Made), '__hash_consed_~w/~d', [Name, Arity]),
  763        assertz(id_constructor(Made, Name, Arity)),
  764        assertz(known_id_name(Name, Arity, Made)),
  765        IdName = Made
  766    ).
  767
  768%   ---- the templates of a constructor ----
  769
  770%   A TEMPLATE IS KEPT PARSED, `template(Name/Arity, Arguments)` with one of
  771%   `ignored` (for `_`), `root` (for `*`) or a nested template per argument,
  772%   ground, so that two declarations of a constructor compare with `==`.
  773
  774:- dynamic constructor_templates/4.     % constructor_templates(Name, Arity, Templates, Kind)
 declared(+Templates)
The constructors of the list declared for this process, each with its templates in the order of the list.
  781declared(Templates) :-
  782    templates_declared(Templates, hash_consing:declared/1, _).
 templates_known(+Name, +Arity, +Context, -Templates, -Kind)
The declared templates of the constructor, and root_only or shaped; raises when the constructor has none.
  789templates_known(Name, Arity, Context, Templates, Kind) :-
  790    (   constructor_templates(Name, Arity, Templates, Kind)
  791    ->  true
  792    ;   throw(error(existence_error(templates, Name/Arity), context(Context, _)))
  793    ).
 templates_declared(+Templates, +Context, -Constructors)
The list parsed and grouped by constructor, in the order of first appearance; every group checked before any is registered; Constructors the Name/Arity of the groups. Raises, registering nothing, on a malformed or unreachable template and on a constructor already declared with another list.
  803templates_declared(Templates, Context, Constructors) :-
  804    must_be(list, Templates),
  805    templates_parsed(Templates, Context, Parsed),
  806    parsed_constructors(Parsed, [], Reversed),
  807    reverse(Reversed, Constructors),
  808    constructors_groups(Constructors, Parsed, Groups),
  809    with_mutex(hash_consing_templates, groups_declared(Groups, Context)).
  810
  811%   groups_declared(+Groups, +Context): the groups checked against the lists
  812%   already declared, then registered, as one step under the mutex
  813%   `hash_consing_templates`. Two threads declaring a constructor at once
  814%   would otherwise both find it undeclared and both register it (measured
  815%   2026-09-30: eight threads declaring the same 2000 constructors left 3924
  816%   registrations and 3631 Id names, and is_id/1 answered once for each Id
  817%   name of its constructor, since id_parts/4 reads them).
  818
  819groups_declared(Groups, Context) :-
  820    groups_checked(Groups, Context),
  821    groups_registered(Groups).
  822
  823parsed_constructors([], Constructors, Constructors).
  824parsed_constructors([_-template(Constructor, _) | Parsed], Seen, Constructors) :-
  825    (   memberchk(Constructor, Seen)
  826    ->  parsed_constructors(Parsed, Seen, Constructors)
  827    ;   parsed_constructors(Parsed, [Constructor | Seen], Constructors)
  828    ).
  829
  830%   A GROUP is `group(Name/Arity, Written, Templates)`: the templates of one
  831%   constructor as written and as parsed, in the order of the list.
  832
  833constructors_groups([], _, []).
  834constructors_groups([Constructor | Constructors], Parsed, [group(Constructor, Written, Templates) | Groups]) :-
  835    constructor_templates_parsed(Parsed, Constructor, Written, Templates),
  836    constructors_groups(Constructors, Parsed, Groups).
  837
  838constructor_templates_parsed([], _, [], []).
  839constructor_templates_parsed([Template-Parsed | Pairs], Constructor, Written, Templates) :-
  840    (   Parsed = template(Constructor, _)
  841    ->  Written = [Template | WrittenRest],
  842        Templates = [Parsed | TemplatesRest]
  843    ;   Written = WrittenRest,
  844        Templates = TemplatesRest
  845    ),
  846    constructor_templates_parsed(Pairs, Constructor, WrittenRest, TemplatesRest).
  847
  848groups_checked([], _).
  849groups_checked([group(Name/Arity, Written, Templates) | Groups], Context) :-
  850    templates_reachable(Written, Templates, [], Context),
  851    (   constructor_templates(Name, Arity, Known, _),
  852        Known \== Templates
  853    ->  templates_written(Known, KnownWritten),
  854        throw(error(permission_error(reconfigure, interned_constructor, Name/Arity),
  855                    context(Context, configured(KnownWritten)-declared(Written))))
  856    ;   true
  857    ),
  858    groups_checked(Groups, Context).
 templates_reachable(+Written, +Templates, +Earlier, +Context)
No template is always taken first by one template before it.
  864templates_reachable([], [], _, _).
  865templates_reachable([Template | Written], [Parsed | Templates], Earlier, Context) :-
  866    (   member(EarlierTemplate, Earlier),
  867        template_covers(EarlierTemplate, Parsed)
  868    ->  throw(error(domain_error(reachable_template, Template), context(Context, _)))
  869    ;   templates_reachable(Written, Templates, [Parsed | Earlier], Context)
  870    ).
 template_covers(+Covering, +Covered)
Every term Covered matches, Covering matches too.
  876template_covers(template(Constructor, Coverings), template(Constructor, Covereds)) :-
  877    arguments_cover(Coverings, Covereds).
  878
  879arguments_cover([], []).
  880arguments_cover([Covering | Coverings], [Covered | Covereds]) :-
  881    argument_covers(Covering, Covered),
  882    arguments_cover(Coverings, Covereds).
  883
  884argument_covers(ignored, _).
  885argument_covers(root, _).
  886argument_covers(template(Constructor, Coverings), template(Constructor, Covereds)) :-
  887    arguments_cover(Coverings, Covereds).
  888
  889groups_registered([]).
  890groups_registered([group(Name/Arity, _, Templates) | Groups]) :-
  891    (   constructor_templates(Name, Arity, _, _)
  892    ->  true
  893    ;   templates_kind(Templates, Kind),
  894        id_name(Name, Arity, _),
  895        assertz(constructor_templates(Name, Arity, Templates, Kind))
  896    ),
  897    groups_registered(Groups).
  898
  899%   A constructor whose one template records nothing has Ids without a
  900%   shape; any other list has a template that records something.
  901
  902templates_kind([template(_, Arguments)], root_only) :-
  903    all_ignored(Arguments),
  904    !.
  905templates_kind(_, shaped).
  906
  907all_ignored([]).
  908all_ignored([ignored | Arguments]) :-
  909    all_ignored(Arguments).
  910
  911compound_name_arguments_or_atom(Term, Name, Arguments) :-
  912    (   atom(Term)
  913    ->  Name = Term,
  914        Arguments = []
  915    ;   compound_name_arguments(Term, Name, Arguments)
  916    ).
 templates_parsed(+Templates, +Context, -Pairs)
Each template paired with its parsed form, Template-Parsed.
  922templates_parsed([], _, []).
  923templates_parsed([Template | Templates], Context, [Template-Parsed | Pairs]) :-
  924    template_parsed(Template, Context, Parsed),
  925    templates_parsed(Templates, Context, Pairs).
  926
  927template_parsed(Template, Context, _) :-
  928    var(Template),
  929    !,
  930    throw(error(instantiation_error, context(Context, _))).
  931template_parsed(Template, Context, template(Name/Arity, Arguments)) :-
  932    callable(Template),
  933    Template \== (*),
  934    !,
  935    compound_name_arguments_or_atom(Template, Name, TemplateArguments),
  936    length(TemplateArguments, Arity),
  937    arguments_parsed(TemplateArguments, Context, Arguments).
  938template_parsed(Template, Context, _) :-
  939    throw(error(type_error(constructor_template, Template), context(Context, _))).
  940
  941arguments_parsed([], _, []).
  942arguments_parsed([Argument | Arguments], Context, [Parsed | Parseds]) :-
  943    argument_parsed(Argument, Context, Parsed),
  944    arguments_parsed(Arguments, Context, Parseds).
  945
  946argument_parsed(Argument, _, ignored) :-
  947    var(Argument),
  948    !.
  949argument_parsed(*, _, root) :-
  950    !.
  951argument_parsed(Argument, Context, Parsed) :-
  952    template_parsed(Argument, Context, Parsed).
 templates_written(+Templates, -Written)
Parsed templates written back as a user writes them, for an error to show.
  958templates_written([], []).
  959templates_written([Template | Templates], [Written | Writtens]) :-
  960    template_written(Template, Written),
  961    templates_written(Templates, Writtens).
  962
  963template_written(template(Name/_, Arguments), Written) :-
  964    arguments_written(Arguments, WrittenArguments),
  965    (   WrittenArguments == []
  966    ->  Written = Name
  967    ;   compound_name_arguments(Written, Name, WrittenArguments)
  968    ).
  969
  970arguments_written([], []).
  971arguments_written([Argument | Arguments], [Written | Writtens]) :-
  972    argument_written(Argument, Written),
  973    arguments_written(Arguments, Writtens).
  974
  975argument_written(ignored, _).
  976argument_written(root, *).
  977argument_written(template(Constructor, Arguments), Written) :-
  978    template_written(template(Constructor, Arguments), Written).
  979
  980%   ---- shapes ----
  981
  982%   A STRUCTURE is what a shape atom spells: a list, one description per
  983%   recorded argument, `other` for an argument that stands for a number or a
  984%   string and `constructor(Name/Arity, Inner)` otherwise, Inner being `none`
  985%   where nothing is recorded below and the list of the descriptions below
  986%   otherwise. While a value leaves a recorded root open its description is
  987%   `unknown`, and a structure holding one is not spelled.
  988%
  989%   THE SAME COMPARISON serves the rewrite, on the source pattern of an
  990%   occurrence, and the store, on a term whose arguments are represented: a
  991%   value's root and arguments are read off a plain term or a pattern as they
  992%   are and off an Id through the store, and a variable, or the arguments of
  993%   an Id pattern whose handle is unbound, are open.
 value_classified(+Templates, +Value, -Class, -Structure)
Class is must_intern when a template matches every term Value stands for, must_not_intern when no template matches any, undetermined otherwise. Structure is what the first template that matches some such term records, when that template matches them all and every root it records is known; unknown otherwise.
 1003value_classified(Templates, Value, Class, Structure) :-
 1004    templates_relations(Templates, Value, Relations),
 1005    relations_class(Relations, Class),
 1006    relations_structure(Relations, Structure).
 1007
 1008templates_relations([], _, []).
 1009templates_relations([Template | Templates], Value, [Relation-Descriptions | Relations]) :-
 1010    template_relation(Template, Value, Relation, Descriptions),
 1011    templates_relations(Templates, Value, Relations).
 1012
 1013relations_class(Relations, must_intern) :-
 1014    memberchk(instance-_, Relations),
 1015    !.
 1016relations_class(Relations, must_not_intern) :-
 1017    \+ memberchk(overlap-_, Relations),
 1018    !.
 1019relations_class(_, undetermined).
 1020
 1021relations_structure([], unknown).
 1022relations_structure([Relation-Descriptions | Relations], Structure) :-
 1023    (   Relation == disjoint
 1024    ->  relations_structure(Relations, Structure)
 1025    ;   Relation == instance,
 1026        descriptions_known(Descriptions)
 1027    ->  Structure = Descriptions
 1028    ;   Structure = unknown
 1029    ).
 1030
 1031descriptions_known([]).
 1032descriptions_known([Description | Descriptions]) :-
 1033    description_known(Description),
 1034    descriptions_known(Descriptions).
 1035
 1036description_known(other).
 1037description_known(constructor(_, Inner)) :-
 1038    (   Inner == none
 1039    ->  true
 1040    ;   is_list(Inner),
 1041        descriptions_known(Inner)
 1042    ).
 first_template_recorded(+Templates, +Term, -Structure)
What the first template that matches the ground Term records; fails when none does.
 1049first_template_recorded([Template | Templates], Term, Structure) :-
 1050    template_relation(Template, Term, Relation, Descriptions),
 1051    (   Relation == instance
 1052    ->  Structure = Descriptions
 1053    ;   first_template_recorded(Templates, Term, Structure)
 1054    ).
 template_relation(+Template, +Value, -Relation, -Descriptions)
Whether every term Value stands for matches the template (instance), none does (disjoint) or some do (overlap), and what the template records on them. Value's own root is the template's constructor.
 1062template_relation(template(_, TemplateArguments), Value, Relation, Descriptions) :-
 1063    compound_name_arguments_or_atom(Value, _, Arguments),
 1064    arguments_relation(TemplateArguments, Arguments, instance, Relation, [], Reversed),
 1065    reverse(Reversed, Descriptions).
 1066
 1067arguments_relation([], [], Relation, Relation, Descriptions, Descriptions).
 1068arguments_relation([TemplateArgument | TemplateArguments], [Argument | Arguments],
 1069                   Relation0, Relation, Descriptions0, Descriptions) :-
 1070    argument_relation(TemplateArgument, Argument, ArgumentRelation, Descriptions0, Descriptions1),
 1071    relation_combined(Relation0, ArgumentRelation, Relation1),
 1072    arguments_relation(TemplateArguments, Arguments, Relation1, Relation, Descriptions1, Descriptions).
 1073
 1074argument_relation(ignored, _, instance, Descriptions, Descriptions).
 1075argument_relation(root, Argument, instance, Descriptions, [Description | Descriptions]) :-
 1076    value_root(Argument, Root),
 1077    root_description(Root, Description).
 1078argument_relation(template(Name/Arity, TemplateArguments), Argument, Relation,
 1079                  Descriptions, [Description | Descriptions]) :-
 1080    value_view(Argument, View),
 1081    nested_relation(View, Name, Arity, TemplateArguments, Relation, Description).
 1082
 1083nested_relation(open, Name, Arity, _, overlap, constructor(Name/Arity, unknown)).
 1084nested_relation(other, Name, Arity, _, disjoint, constructor(Name/Arity, unknown)).
 1085nested_relation(layer(ViewName/ViewArity, Arguments), Name, Arity, TemplateArguments,
 1086                Relation, constructor(Name/Arity, Inner)) :-
 1087    (   ViewName/ViewArity == Name/Arity
 1088    ->  arguments_relation(TemplateArguments, Arguments, instance, Relation, [], Reversed),
 1089        reverse(Reversed, Descriptions),
 1090        (   Descriptions == []
 1091        ->  Inner = none
 1092        ;   Inner = Descriptions
 1093        )
 1094    ;   Relation = disjoint,
 1095        Inner = unknown
 1096    ).
 1097
 1098relation_combined(disjoint, _, disjoint) :-
 1099    !.
 1100relation_combined(_, disjoint, disjoint) :-
 1101    !.
 1102relation_combined(instance, instance, instance) :-
 1103    !.
 1104relation_combined(_, _, overlap).
 1105
 1106root_description(open, unknown).
 1107root_description(other, other).
 1108root_description(Name/Arity, constructor(Name/Arity, none)).
 value_root(+Value, -Root)
The root constructor of the term Value stands for, Name/Arity, read off an Id's functor name without a lookup; open for a variable, other for a number or a string.
 1116value_root(Value, open) :-
 1117    var(Value),
 1118    !.
 1119value_root(Value, Name/Arity) :-
 1120    id_parts(Value, IdName, _, _),
 1121    !,
 1122    id_constructor(IdName, Name, Arity).
 1123value_root(Value, Name/Arity) :-
 1124    compound(Value),
 1125    !,
 1126    compound_name_arity(Value, Name, Arity).
 1127value_root(Value, Value/0) :-
 1128    atom(Value),
 1129    !.
 1130value_root(_, other).
 value_view(+Value, -View)
layer(Name/Arity, Arguments), the root and the arguments of the term Value stands for, an Id's read from the store and open while its handle is unbound; open for a variable; other for a number or a string.
 1138value_view(Value, open) :-
 1139    var(Value),
 1140    !.
 1141value_view(Value, layer(Name/Arity, Arguments)) :-
 1142    id_parts(Value, IdName, _, Handle),
 1143    !,
 1144    id_constructor(IdName, Name, Arity),
 1145    (   nonvar(Handle)
 1146    ->  trie_term(Handle, Stored),
 1147        compound_name_arguments_or_atom(Stored, _, Arguments)
 1148    ;   length(Arguments, Arity)
 1149    ).
 1150value_view(Value, layer(Name/Arity, Arguments)) :-
 1151    compound(Value),
 1152    !,
 1153    compound_name_arguments(Value, Name, Arguments),
 1154    length(Arguments, Arity).
 1155value_view(Value, layer(Value/0, [])) :-
 1156    atom(Value),
 1157    !.
 1158value_view(_, other).
 structure_spelled(+Structure, -Atom)
The shape atom of a structure, remembered in the trie of spellings so that it is built once.
 1165structure_spelled(Structure, Atom) :-
 1166    process_stores(_, Spellings),
 1167    (   trie_lookup(Spellings, Structure, Known)
 1168    ->  Atom = Known
 1169    ;   with_mutex(hash_consing_spellings, spelling_inserted(Spellings, Structure, Atom))
 1170    ).
 1171
 1172%   spelling_inserted(+Spellings, +Structure, -Atom): the spelling looked up
 1173%   again and, when it is still absent, made and inserted, under the mutex
 1174%   `hash_consing_spellings`. Two threads that both miss a structure would
 1175%   otherwise both insert it, and the second trie_insert/3 fails, which
 1176%   fails its interning (measured 2026-09-30: eight threads spelling the
 1177%   same 2000 shapes).
 1178
 1179spelling_inserted(Spellings, Structure, Atom) :-
 1180    (   trie_lookup(Spellings, Structure, Known)
 1181    ->  Atom = Known
 1182    ;   structure_atom(Structure, Atom),
 1183        trie_insert(Spellings, Structure, Atom)
 1184    ).
 structure_atom(+Structure, -Atom)
The spelling: descriptions joined by commas, - for other, Name/Arity and, in parentheses, what is below.
 1191structure_atom(Structure, Atom) :-
 1192    descriptions_texts(Structure, Texts),
 1193    atomic_list_concat(Texts, ',', Atom).
 1194
 1195descriptions_texts([], []).
 1196descriptions_texts([Description | Descriptions], [Text | Texts]) :-
 1197    description_text(Description, Text),
 1198    descriptions_texts(Descriptions, Texts).
 1199
 1200description_text(other, '-').
 1201description_text(constructor(Name/Arity, none), Text) :-
 1202    !,
 1203    format(atom(Text), '~w/~d', [Name, Arity]).
 1204description_text(constructor(Name/Arity, Inner), Text) :-
 1205    structure_atom(Inner, InnerText),
 1206    format(atom(Text), '~w/~d(~w)', [Name, Arity, InnerText]).
 1207
 1208%   ---- the boundary ----
 externalized(+TermWithIds, -External)
Every Id replaced by its term, recursively.
 1214externalized(Term, External) :-
 1215    var(Term),
 1216    !,
 1217    External = Term.
 1218externalized(Term, External) :-
 1219    id_parts(Term, _, _, Handle),
 1220    !,
 1221    (   nonvar(Handle)
 1222    ->  trie_term(Handle, Stored),
 1223        externalized(Stored, External)
 1224    ;   throw(error(instantiation_error, context(hash_consing:externalized/2, Term)))
 1225    ).
 1226externalized(Term, External) :-
 1227    compound(Term),
 1228    !,
 1229    compound_name_arguments(Term, Name, Arguments),
 1230    arguments_externalized(Arguments, ExternalArguments),
 1231    compound_name_arguments(External, Name, ExternalArguments).
 1232externalized(Term, Term).
 1233
 1234arguments_externalized([], []).
 1235arguments_externalized([Argument | Arguments], [External | Externals]) :-
 1236    externalized(Argument, External),
 1237    arguments_externalized(Arguments, Externals).
 internalized(+Templates, +External, -TermWithIds)
The templates declared as declared/1 declares them, then every instance of their constructors represented, bottom up: its Id when a template matches it, the plain term otherwise; an Id is kept as it is.
 1245internalized(Templates, External, TermWithIds) :-
 1246    templates_declared(Templates, hash_consing:internalized/3, Constructors),
 1247    term_internalized(External, Constructors, TermWithIds).
 1248
 1249term_internalized(Term, _, Internal) :-
 1250    var(Term),
 1251    !,
 1252    Internal = Term.
 1253term_internalized(Term, _, Internal) :-
 1254    id_parts(Term, _, _, _),
 1255    !,
 1256    Internal = Term.
 1257term_internalized(Term, Indicators, Internal) :-
 1258    compound(Term),
 1259    !,
 1260    compound_name_arguments(Term, Name, Arguments),
 1261    arguments_internalized(Arguments, Indicators, InternalArguments),
 1262    compound_name_arguments(Rebuilt, Name, InternalArguments),
 1263    compound_name_arity(Term, Name, Arity),
 1264    instance_internalized(Name/Arity, Indicators, Rebuilt, Internal).
 1265term_internalized(Term, Indicators, Internal) :-
 1266    atom(Term),
 1267    !,
 1268    instance_internalized(Term/0, Indicators, Term, Internal).
 1269term_internalized(Term, _, Term).
 1270
 1271arguments_internalized([], _, []).
 1272arguments_internalized([Argument | Arguments], Indicators, [Internal | Internals]) :-
 1273    term_internalized(Argument, Indicators, Internal),
 1274    arguments_internalized(Arguments, Indicators, Internals).
 1275
 1276%   An instance whose templates were given is represented; one that is not
 1277%   ground stays plain when no completion of it can match, and raises
 1278%   otherwise, as intern/2 does on a term that is not ground.
 1279
 1280instance_internalized(Name/Arity, Constructors, Layer, Internal) :-
 1281    (   memberchk(Name/Arity, Constructors)
 1282    ->  (   ground(Layer)
 1283        ->  (   upserted(Layer, Id)
 1284            ->  Internal = Id
 1285            ;   Internal = Layer
 1286            )
 1287        ;   constructor_templates(Name, Arity, Templates, _),
 1288            value_classified(Templates, Layer, Class, _),
 1289            (   Class == must_not_intern
 1290            ->  Internal = Layer
 1291            ;   throw(error(instantiation_error, context(hash_consing:internalized/3, Layer)))
 1292            )
 1293        )
 1294    ;   Internal = Layer
 1295    ).
 1296
 1297%   ---- the directive ----
 rewritten(+Templates)
The file being loaded opts in for the constructors whose templates are in the list; several calls add up. The same as rewritten(Templates, []).
 rewritten(+Templates, +Options)
rewritten/1 with Options, of which there is one:
layer_arguments(Skeletons)
Each skeleton is a head or goal, `Name(A1, ..., An)`, with the atom layer at the argument positions that hold a LAYER and _ elsewhere. In every head and body goal of the file with that name and arity, the term at a layer position keeps its own constructor as written, a plain term, and only its arguments are rewritten, as represented/2 reads its first argument. This is how a file that opts in calls a predicate of a file that does not, one that hands out or takes the plain layer of a listed constructor.
 1318:- dynamic registered_constructor/3.     % registered_constructor(SourceFile, Name, Arity)
 1319:- dynamic registered_layer_positions/4. % registered_layer_positions(SourceFile, Name, Arity, Positions)
 1320
 1321rewritten(Templates) :-
 1322    rewritten_with_options(Templates, [], hash_consing:rewritten/1).
 1323
 1324rewritten(Templates, Options) :-
 1325    rewritten_with_options(Templates, Options, hash_consing:rewritten/2).
 1326
 1327rewritten_with_options(Templates, Options, Context) :-
 1328    must_be(list, Templates),
 1329    must_be(list, Options),
 1330    options_checked(Options, Context, Layered),
 1331    (   prolog_load_context(source, File)
 1332    ->  true
 1333    ;   throw(error(context_error(nodirective, Context), _))
 1334    ),
 1335    templates_declared(Templates, Context, Constructors),
 1336    constructors_registered(Constructors, File),
 1337    layered_registered(Layered, File, Context).
 1338
 1339options_checked([], _, []).
 1340options_checked([Option | Options], Context, Layered) :-
 1341    (   Option = layer_arguments(Skeletons),
 1342        is_list(Skeletons)
 1343    ->  skeletons_read(Skeletons, Context, Layered, Rest),
 1344        options_checked(Options, Context, Rest)
 1345    ;   throw(error(domain_error(rewritten_option, Option), context(Context, _)))
 1346    ).
 1347
 1348%   skeletons_read(+Skeletons, +Context, -Layered, ?Rest): each skeleton
 1349%   read as `layered(Name, Arity, Positions)`, its layer positions, in front
 1350%   of Rest.
 1351
 1352skeletons_read([], _, Rest, Rest).
 1353skeletons_read([Skeleton | Skeletons], Context, [layered(Name, Arity, Positions) | Layered], Rest) :-
 1354    (   compound(Skeleton),
 1355        compound_name_arguments(Skeleton, Name, Arguments),
 1356        layer_markers(Arguments, 1, Positions),
 1357        Positions \== []
 1358    ->  length(Arguments, Arity)
 1359    ;   throw(error(domain_error(layer_skeleton, Skeleton), context(Context, _)))
 1360    ),
 1361    skeletons_read(Skeletons, Context, Layered, Rest).
 1362
 1363%   layered_registered(+Layered, +File, +Context): the layer positions
 1364%   recorded for File; a second skeleton of the same name and arity in one
 1365%   file must give the same positions.
 1366
 1367layered_registered([], _, _).
 1368layered_registered([layered(Name, Arity, Positions) | Layered], File, Context) :-
 1369    (   registered_layer_positions(File, Name, Arity, Known)
 1370    ->  (   Known == Positions
 1371        ->  true
 1372        ;   throw(error(permission_error(reconfigure, layer_arguments, Name/Arity),
 1373                        context(Context, _)))
 1374        )
 1375    ;   assertz(registered_layer_positions(File, Name, Arity, Positions))
 1376    ),
 1377    layered_registered(Layered, File, Context).
 1378
 1379%   layer_markers(+Arguments, +Position, -Positions): the positions, from
 1380%   Position on, of the arguments that are the atom `layer`; any other
 1381%   argument must be a variable.
 1382
 1383layer_markers([], _, []).
 1384layer_markers([Argument | Arguments], Position, Positions) :-
 1385    Next is Position + 1,
 1386    (   Argument == layer
 1387    ->  Positions = [Position | Rest]
 1388    ;   var(Argument)
 1389    ->  Positions = Rest
 1390    ),
 1391    layer_markers(Arguments, Next, Rest).
 1392
 1393constructors_registered([], _).
 1394constructors_registered([Name/Arity | Constructors], File) :-
 1395    (   registered_constructor(File, Name, Arity)
 1396    ->  true
 1397    ;   assertz(registered_constructor(File, Name, Arity))
 1398    ),
 1399    constructors_registered(Constructors, File).
 1400
 1401%   ---- the rewrite ----
 source_rewritten(+Source, -Rewritten)
The rewrite of one term read from a file that opted in; fails, leaving the term to SWI-Prolog, otherwise.
 1408source_rewritten(Source, _) :-
 1409    var(Source),
 1410    !,
 1411    fail.
 1412source_rewritten(end_of_file, _) :-
 1413    !,
 1414    prolog_load_context(source, File),
 1415    prolog_load_context(file, File),
 1416    retractall(registered_constructor(File, _, _)),
 1417    retractall(registered_layer_positions(File, _, _, _)),
 1418    fail.
 1419source_rewritten((:- _), _) :-
 1420    !,
 1421    fail.
 1422source_rewritten((?- _), _) :-
 1423    !,
 1424    fail.
 1425source_rewritten(Source, Rewritten) :-
 1426    prolog_load_context(source, File),
 1427    registered_constructor(File, _, _),
 1428    !,
 1429    clause_rewritten(Source, File, Candidate),
 1430    Candidate \=@= Source,
 1431    Rewritten = Candidate.
 1432
 1433clause_rewritten((Head --> Body), File, (Head --> Body)) :-
 1434    !,
 1435    listed_absent((Head --> Body), File, dcg_rule).
 1436clause_rewritten((Head :- Body), File, Rewritten) :-
 1437    !,
 1438    rule_rewritten(Head, Body, File, Rewritten).
 1439clause_rewritten(Head, File, Rewritten) :-
 1440    rule_rewritten(Head, true, File, Rewritten).
 rule_rewritten(+Head, +Body, +File, -Rewritten)
The head's occurrences scheduled around the rewritten body.
 1446rule_rewritten(Qualifier:Head0, Body0, File, (Qualifier:Head :- Body)) :-
 1447    !,
 1448    plain_rule_rewritten(Head0, Body0, File, Head, Body).
 1449rule_rewritten(Head0, Body0, File, (Head :- Body)) :-
 1450    plain_rule_rewritten(Head0, Body0, File, Head, Body).
 1451
 1452plain_rule_rewritten(Head0, Body0, File, Head, Body) :-
 1453    file_layer_positions(Head0, File, Positions),
 1454    arguments_of_abstracted(Head0, Positions, File, Head, Occurrences),
 1455    term_singletons(Head0-Body0, Singletons),
 1456    body_rewritten(Body0, rewriting(File, Singletons), RewrittenBody),
 1457    head_scheduled(Occurrences, Head-RewrittenBody, RewrittenBody, Body).
 arguments_of_abstracted(+Callable0, +LayerPositions, +File, -Callable, -Occurrences)
The arguments of a head or a goal abstracted (its own functor is a predicate and is left alone; an argument at one of LayerPositions keeps its own constructor), the ground occurrences represented now, and the others listed bottom up as occurrence(Class, Layer, Representation, Handle): Class id for an occurrence that must be interned, its Representation an Id pattern and Handle that pattern's handle, and undetermined(IdName) for an undetermined one, its Representation and its Handle the fresh variable written where it stood. An occurrence that must not be interned is its layer, written in place, and is not listed.
 1472arguments_of_abstracted(Callable0, LayerPositions, File, Callable, Occurrences) :-
 1473    compound(Callable0),
 1474    !,
 1475    compound_name_arguments(Callable0, Name, Arguments0),
 1476    positioned_arguments_abstracted(Arguments0, 1, LayerPositions, File, Arguments, [], Reversed),
 1477    compound_name_arguments(Callable, Name, Arguments),
 1478    reverse(Reversed, BottomUp),
 1479    occurrences_baked(BottomUp, Occurrences).
 1480arguments_of_abstracted(Callable, _, _, Callable, []).
 1481
 1482%   file_layer_positions(+Callable, +File, -Positions): the layer positions
 1483%   File registered for Callable's name and arity (rewritten/2), none when
 1484%   it registered none.
 1485
 1486file_layer_positions(Callable, File, Positions) :-
 1487    (   compound(Callable),
 1488        compound_name_arity(Callable, Name, Arity),
 1489        registered_layer_positions(File, Name, Arity, Registered)
 1490    ->  Positions = Registered
 1491    ;   Positions = []
 1492    ).
 1493
 1494%   positioned_arguments_abstracted(+Arguments0, +Position, +LayerPositions,
 1495%   +File, -Arguments, +Reversed0, -Reversed): arguments_abstracted/5, the
 1496%   argument at a layer position kept as a plain layer: its own
 1497%   constructor as written, its arguments abstracted.
 1498
 1499positioned_arguments_abstracted([], _, _, _, [], Reversed, Reversed).
 1500positioned_arguments_abstracted([Argument0 | Arguments0], Position, LayerPositions, File,
 1501                                [Argument | Arguments], Reversed0, Reversed) :-
 1502    (   memberchk(Position, LayerPositions)
 1503    ->  layer_abstracted(Argument0, File, Argument, Reversed0, Reversed1)
 1504    ;   abstracted(Argument0, File, Argument, Reversed0, Reversed1)
 1505    ),
 1506    Next is Position + 1,
 1507    positioned_arguments_abstracted(Arguments0, Next, LayerPositions, File,
 1508                                    Arguments, Reversed1, Reversed).
 1509
 1510layer_abstracted(Layer0, File, Layer, Reversed0, Reversed) :-
 1511    (   compound(Layer0)
 1512    ->  compound_name_arguments(Layer0, Name, Arguments0),
 1513        arguments_abstracted(Arguments0, File, Arguments, Reversed0, Reversed),
 1514        compound_name_arguments(Layer, Name, Arguments)
 1515    ;   Layer = Layer0,
 1516        Reversed = Reversed0
 1517    ).
 1518
 1519arguments_abstracted([], _, [], Reversed, Reversed).
 1520arguments_abstracted([Argument0 | Arguments0], File, [Argument | Arguments], Reversed0, Reversed) :-
 1521    abstracted(Argument0, File, Argument, Reversed0, Reversed1),
 1522    arguments_abstracted(Arguments0, File, Arguments, Reversed1, Reversed).
 abstracted(+Term0, +File, -Term, +Reversed0, -Reversed)
Each occurrence of a listed constructor replaced by its representation in the clause, its subterms first, the listed occurrences prepended as they are met.
 1530abstracted(Term, _, Term, Reversed, Reversed) :-
 1531    var(Term),
 1532    !.
 1533abstracted(Term0, File, Term, Reversed0, Reversed) :-
 1534    compound(Term0),
 1535    !,
 1536    compound_name_arguments(Term0, Name, Arguments0),
 1537    arguments_abstracted(Arguments0, File, Arguments, Reversed0, Reversed1),
 1538    compound_name_arguments(Layer, Name, Arguments),
 1539    compound_name_arity(Term0, Name, Arity),
 1540    occurrence_abstracted(Name, Arity, Term0, Layer, File, Term, Reversed1, Reversed).
 1541abstracted(Term0, File, Term, Reversed0, Reversed) :-
 1542    atom(Term0),
 1543    !,
 1544    occurrence_abstracted(Term0, 0, Term0, Term0, File, Term, Reversed0, Reversed).
 1545abstracted(Term, _, Term, Reversed, Reversed).
 occurrence_abstracted(+Name, +Arity, +Source, +Layer, +File, -Representation, +Reversed0, -Reversed)
AN OCCURRENCE IS CLASSIFIED (above) by its source pattern Source, and written as an Id pattern, as its layer, or as a fresh variable.
 1552occurrence_abstracted(Name, Arity, Source, Layer, File, Representation, Reversed0, Reversed) :-
 1553    (   registered_constructor(File, Name, Arity)
 1554    ->  constructor_templates(Name, Arity, Templates, Kind),
 1555        value_classified(Templates, Source, Class, Structure),
 1556        occurrence_represented(Class, Name, Arity, Kind, Structure, Layer, Representation,
 1557                               Reversed0, Reversed)
 1558    ;   Representation = Layer,
 1559        Reversed = Reversed0
 1560    ).
 1561
 1562occurrence_represented(must_intern, Name, Arity, Kind, Structure, Layer, Id,
 1563                       Reversed, [occurrence(id, Layer, Id, Handle) | Reversed]) :-
 1564    id_built(Name, Arity, Kind, Structure, Handle, Id).
 1565occurrence_represented(must_not_intern, _, _, _, _, Layer, Layer, Reversed, Reversed).
 1566occurrence_represented(undetermined, Name, Arity, _, _, Layer, Variable,
 1567                       Reversed, [occurrence(undetermined(IdName), Layer, Variable, Variable) | Reversed]) :-
 1568    id_name(Name, Arity, IdName).
 occurrences_baked(+BottomUp, -Remaining)
An occurrence whose layer is ground is represented while the file loads, which binds its Id in the clause; bottom up, so a parent of baked occurrences may become ground in turn.
 1576occurrences_baked([], []).
 1577occurrences_baked([Occurrence | Occurrences], Remaining) :-
 1578    Occurrence = occurrence(Class, Layer, Representation, _),
 1579    (   ground(Layer)
 1580    ->  occurrence_represented_now(Class, Layer, Representation),
 1581        occurrences_baked(Occurrences, Remaining)
 1582    ;   Remaining = [Occurrence | Rest],
 1583        occurrences_baked(Occurrences, Rest)
 1584    ).
 1585
 1586occurrence_represented_now(id, Layer, Id) :-
 1587    intern(Layer, Id).
 1588occurrence_represented_now(undetermined(_), Layer, Variable) :-
 1589    represented(Layer, Variable).
 head_scheduled(+BottomUp, +Clause, +Body0, -Body)
The head row of THE REWRITE OF A CLAUSE (above). Clause is the rewritten head paired with Body0, read only to find the occurrences whose layer nothing reads.
 1597head_scheduled([], _, Body, Body) :-
 1598    !.
 1599head_scheduled(BottomUp, Clause, Body0, (Condition -> Fast ; General)) :-
 1600    reverse(BottomUp, TopDown),
 1601    top_level_occurrences(BottomUp, BottomUp, TopLevel),
 1602    occurrences_given(TopLevel, Condition),
 1603    occurrences_read(TopDown, Clause-BottomUp, Read),
 1604    occurrences_goal(Read, related, Lookups),
 1605    occurrences_goal(TopDown, related_if_given, Settled),
 1606    occurrences_goal(BottomUp, represented_if_ground, Inserted),
 1607    occurrences_goal(BottomUp, settled_unless_given, Finished),
 1608    conjoined([Lookups, Body0], Fast),
 1609    conjoined([Settled, Inserted, Body0, Finished], General).
 conjoined(+Goals, -Conjunction)
The goals in order, true left out.
 1615conjoined(Goals, Conjunction) :-
 1616    goals_kept(Goals, Kept),
 1617    kept_conjoined(Kept, Conjunction).
 1618
 1619goals_kept([], []).
 1620goals_kept([Goal | Goals], Kept) :-
 1621    (   Goal == true
 1622    ->  Kept = Rest
 1623    ;   Kept = [Goal | Rest]
 1624    ),
 1625    goals_kept(Goals, Rest).
 1626
 1627kept_conjoined([], true).
 1628kept_conjoined([Goal], Goal) :-
 1629    !.
 1630kept_conjoined([Goal | Goals], (Goal, Conjunction)) :-
 1631    kept_conjoined(Goals, Conjunction).
 1632
 1633%   occurrences_read(+Occurrences, +Clause, -Read): the occurrences whose
 1634%   layer the clause reads. A given Id's layer is looked up only to bind the
 1635%   layer's arguments; an Id occurrence whose layer is its constructor over
 1636%   variables that occur nowhere else in Clause (the head, the body and the
 1637%   layers of the listed occurrences) binds nothing anyone reads, and is left
 1638%   out of the fast path. Its Id pattern in the head already selects the
 1639%   constructor and the shape. Every other occurrence is kept.
 1640
 1641occurrences_read(Occurrences, Clause, Read) :-
 1642    variables_occurring_more_than_once(Clause, Shared),
 1643    occurrences_kept_when_read(Occurrences, Shared, Read).
 1644
 1645occurrences_kept_when_read([], _, []).
 1646occurrences_kept_when_read([Occurrence | Occurrences], Shared, Read) :-
 1647    (   Occurrence = occurrence(id, Layer, _, _),
 1648        layer_arguments(Layer, Arguments),
 1649        unread_arguments(Arguments, Shared)
 1650    ->  Read = Rest
 1651    ;   Read = [Occurrence | Rest]
 1652    ),
 1653    occurrences_kept_when_read(Occurrences, Shared, Rest).
 1654
 1655layer_arguments(Layer, Arguments) :-
 1656    (   atom(Layer)
 1657    ->  Arguments = []
 1658    ;   compound_name_arguments(Layer, _, Arguments)
 1659    ).
 1660
 1661unread_arguments([], _).
 1662unread_arguments([Argument | Arguments], Shared) :-
 1663    var(Argument),
 1664    \+ variable_member(Argument, Shared),
 1665    unread_arguments(Arguments, Shared).
 1666
 1667%   variables_occurring_more_than_once(+Term, -Shared): the variables of Term
 1668%   that occur in it more than once.
 1669
 1670variables_occurring_more_than_once(Term, Shared) :-
 1671    term_singletons(Term, Singletons),
 1672    term_variables(Term, Variables),
 1673    variables_not_in(Variables, Singletons, Shared).
 1674
 1675variables_not_in([], _, []).
 1676variables_not_in([Variable | Variables], Excluded, Kept) :-
 1677    (   variable_member(Variable, Excluded)
 1678    ->  Kept = Rest
 1679    ;   Kept = [Variable | Rest]
 1680    ),
 1681    variables_not_in(Variables, Excluded, Rest).
 1682
 1683%   An occurrence is at the top when its handle is in the layer of no other
 1684%   listed occurrence; the layer of an occurrence that must not be interned
 1685%   is written in place and does not hide what it holds.
 1686
 1687top_level_occurrences([], _, []).
 1688top_level_occurrences([Occurrence | Occurrences], All, TopLevel) :-
 1689    Occurrence = occurrence(_, _, _, Handle),
 1690    (   nested_in_another(Handle, All)
 1691    ->  TopLevel = Rest
 1692    ;   TopLevel = [Occurrence | Rest]
 1693    ),
 1694    top_level_occurrences(Occurrences, All, Rest).
 1695
 1696nested_in_another(Handle, [occurrence(_, Layer, _, _) | Occurrences]) :-
 1697    (   term_variables(Layer, Variables),
 1698        variable_member(Handle, Variables)
 1699    ->  true
 1700    ;   nested_in_another(Handle, Occurrences)
 1701    ).
 1702
 1703variable_member(Variable, [Candidate | Candidates]) :-
 1704    (   Variable == Candidate
 1705    ->  true
 1706    ;   variable_member(Variable, Candidates)
 1707    ).
 occurrences_given(+TopLevel, -Condition)
The condition of the fast path of a head: every top-level occurrence is given.
 1714occurrences_given([Occurrence], Condition) :-
 1715    !,
 1716    occurrence_given(Occurrence, Condition).
 1717occurrences_given([Occurrence | Occurrences], (Condition, Conditions)) :-
 1718    occurrence_given(Occurrence, Condition),
 1719    occurrences_given(Occurrences, Conditions).
 1720
 1721%   An Id pattern is given when its handle is bound; an undetermined
 1722%   occurrence when its variable is bound and not to an Id pattern of its
 1723%   constructor whose handle is unbound. The Ids of a constructor whose
 1724%   interning can be undetermined always have a shape.
 1725
 1726occurrence_given(occurrence(id, _, _, Handle), nonvar(Handle)).
 1727occurrence_given(occurrence(undetermined(IdName), _, Variable, _),
 1728                 (nonvar(Variable), (Variable = Id -> nonvar(Handle) ; true))) :-
 1729    compound_name_arguments(Id, IdName, [_, Handle]).
 occurrences_goal(+Occurrences, +Step, -Goal)
The conjunction of one step over the occurrences, in the order given.
 1735occurrences_goal([], _, true).
 1736occurrences_goal([Occurrence], Step, Goal) :-
 1737    !,
 1738    occurrence_goal(Step, Occurrence, Goal).
 1739occurrences_goal([Occurrence | Occurrences], Step, (Goal, Goals)) :-
 1740    occurrence_goal(Step, Occurrence, Goal),
 1741    occurrences_goal(Occurrences, Step, Goals).
 1742
 1743%   The steps: `related` relates an occurrence to its layer; `related_if_given`
 1744%   does so when it is given; `represented_if_ground` represents it when it is
 1745%   not given and its layer is ground; `settled_unless_given` represents it at
 1746%   the end of a head, where it must be settled; `represented_before_call`
 1747%   interns a ground one and decides an undetermined one before a goal is
 1748%   called.
 1749
 1750occurrence_goal(related, occurrence(id, Layer, Id, _),
 1751                hash_consing:intern(Layer, Id)).
 1752occurrence_goal(related, occurrence(undetermined(_), Layer, Variable, _),
 1753                hash_consing:represented(Layer, Variable)).
 1754occurrence_goal(related_if_given, occurrence(id, Layer, Id, Handle),
 1755                (nonvar(Handle) -> hash_consing:intern(Layer, Id) ; true)).
 1756occurrence_goal(related_if_given, Occurrence,
 1757                (Given -> hash_consing:represented(Layer, Variable) ; true)) :-
 1758    Occurrence = occurrence(undetermined(_), Layer, Variable, _),
 1759    occurrence_given(Occurrence, Given).
 1760occurrence_goal(represented_if_ground, occurrence(id, Layer, Id, Handle),
 1761                ((var(Handle), ground(Layer)) -> hash_consing:intern(Layer, Id) ; true)).
 1762occurrence_goal(represented_if_ground, Occurrence,
 1763                ((\+ Given, ground(Layer)) -> hash_consing:represented(Layer, Variable) ; true)) :-
 1764    Occurrence = occurrence(undetermined(_), Layer, Variable, _),
 1765    occurrence_given(Occurrence, Given).
 1766occurrence_goal(settled_unless_given, occurrence(id, Layer, Id, Handle),
 1767                (var(Handle) -> hash_consing:intern(Layer, Id) ; true)).
 1768occurrence_goal(settled_unless_given, Occurrence,
 1769                (Given -> true ; hash_consing:represented_settled(Layer, Variable))) :-
 1770    Occurrence = occurrence(undetermined(_), Layer, Variable, _),
 1771    occurrence_given(Occurrence, Given).
 1772occurrence_goal(represented_before_call, occurrence(id, Layer, Id, _),
 1773                (ground(Layer) -> hash_consing:intern(Layer, Id) ; true)).
 1774occurrence_goal(represented_before_call, occurrence(undetermined(_), Layer, Variable, _),
 1775                hash_consing:represented(Layer, Variable)).
 body_rewritten(+Body0, +Context, -Body)
Context is rewriting(File, Singletons), Singletons the variables that occur once in the source clause.

The control constructs rewritten inside, every other goal by goal_rewritten/3.

 1785body_rewritten(Goal, _, Goal) :-
 1786    var(Goal),
 1787    !.
 1788body_rewritten((Left0, Right0), Context, (Left, Right)) :-
 1789    !,
 1790    body_rewritten(Left0, Context, Left),
 1791    body_rewritten(Right0, Context, Right).
 1792body_rewritten((Left0 ; Right0), Context, (Left ; Right)) :-
 1793    !,
 1794    body_rewritten(Left0, Context, Left),
 1795    body_rewritten(Right0, Context, Right).
 1796body_rewritten((Condition0 -> Then0), Context, (Condition -> Then)) :-
 1797    !,
 1798    body_rewritten(Condition0, Context, Condition),
 1799    body_rewritten(Then0, Context, Then).
 1800body_rewritten((Condition0 *-> Then0), Context, (Condition *-> Then)) :-
 1801    !,
 1802    body_rewritten(Condition0, Context, Condition),
 1803    body_rewritten(Then0, Context, Then).
 1804body_rewritten(\+ Goal0, Context, \+ Goal) :-
 1805    !,
 1806    body_rewritten(Goal0, Context, Goal).
 1807body_rewritten(call(Goal0), Context, call(Goal)) :-
 1808    !,
 1809    body_rewritten(Goal0, Context, Goal).
 1810body_rewritten(forall(Condition0, Action0), Context, forall(Condition, Action)) :-
 1811    !,
 1812    body_rewritten(Condition0, Context, Condition),
 1813    body_rewritten(Action0, Context, Action).
 1814body_rewritten(findall(Template, Goal0, Result), Context, findall(Template, Goal, Result)) :-
 1815    !,
 1816    context_file(Context, File),
 1817    listed_absent(Template-Result, File, findall_template_or_result),
 1818    body_rewritten(Goal0, Context, Goal).
 1819body_rewritten(catch(Goal0, Catcher, Recovery0), Context, catch(Goal, Catcher, Recovery)) :-
 1820    !,
 1821    context_file(Context, File),
 1822    listed_absent(Catcher, File, catch_catcher),
 1823    body_rewritten(Goal0, Context, Goal),
 1824    body_rewritten(Recovery0, Context, Recovery).
 1825body_rewritten(hash_consing:Goal0, Context, hash_consing:Goal) :-
 1826    templates_first(Goal0),
 1827    !,
 1828    templates_kept(Goal0, Context, Goal).
 1829body_rewritten(hash_consing:Goal0, Context, hash_consing:Goal) :-
 1830    layer_first(Goal0),
 1831    !,
 1832    goal_rewritten(Goal0, [1], Context, Goal).
 1833body_rewritten(Qualifier:Goal0, Context, Qualifier:Goal) :-
 1834    !,
 1835    body_rewritten(Goal0, Context, Goal).
 1836body_rewritten(Goal0, Context, Goal) :-
 1837    templates_first(Goal0),
 1838    prolog_load_context(module, Module),
 1839    predicate_property(Module:Goal0, imported_from(hash_consing)),
 1840    !,
 1841    templates_kept(Goal0, Context, Goal).
 1842body_rewritten(Goal0, Context, Goal) :-
 1843    layer_first(Goal0),
 1844    prolog_load_context(module, Module),
 1845    predicate_property(Module:Goal0, imported_from(hash_consing)),
 1846    !,
 1847    goal_rewritten(Goal0, [1], Context, Goal).
 1848body_rewritten(Goal0, Context, Goal) :-
 1849    context_file(Context, File),
 1850    file_layer_positions(Goal0, File, Positions),
 1851    goal_rewritten(Goal0, Positions, Context, Goal).
 1852
 1853%   templates_first(+Goal): Goal is a predicate of this library whose first
 1854%   argument is a list of templates. The templates are data the library
 1855%   reads, not terms to intern, so a call of it in an opted-in file keeps
 1856%   them as written (templates_kept/3), and only its other arguments are
 1857%   rewritten.
 1858
 1859templates_first(internalized(_, _, _)).
 1860templates_first(declared(_)).
 1861
 1862templates_kept(Goal0, Context, Goal) :-
 1863    compound_name_arguments(Goal0, Name, [Templates | Arguments]),
 1864    compound_name_arguments(StandIn, Name, [Placeholder | Arguments]),
 1865    goal_rewritten(StandIn, [], Context, Goal),
 1866    Placeholder = Templates.
 1867
 1868%   layer_first(+Goal): Goal is a predicate of this library whose first
 1869%   argument is a layer, kept as a plain layer in an opted-in file.
 1870
 1871layer_first(represented(_, _)).
 1872
 1873context_file(rewriting(File, _), File).
 goal_rewritten(+Goal0, +LayerPositions, +Context, -Goal)
The body goal row of THE REWRITE OF A CLAUSE (above), the arguments at LayerPositions kept as plain layers. An Id occurrence whose layer is its constructor over variables that occur once in the source clause is UNREAD: those variables are unbound at the call and nobody reads them after it, so its Id pattern alone, which selects the constructor and the shape, is all the goal needs. It is left out of the schedule; after the call only its handle is checked, and an unbound handle is handed to intern/2, which raises as it would have.
 1886goal_rewritten(Goal0, LayerPositions, rewriting(File, Singletons), Goal) :-
 1887    arguments_of_abstracted(Goal0, LayerPositions, File, Called, BottomUp),
 1888    occurrences_partitioned(BottomUp, Singletons, Read, Unread),
 1889    goal_scheduled(Read, Called, Scheduled),
 1890    unread_checks(Unread, Checks),
 1891    conjoined([Scheduled | Checks], Goal).
 1892
 1893occurrences_partitioned([], _, [], []).
 1894occurrences_partitioned([Occurrence | Occurrences], Singletons, Read, Unread) :-
 1895    (   Occurrence = occurrence(id, Layer, _, _),
 1896        layer_arguments(Layer, Arguments),
 1897        Arguments \== [],
 1898        singleton_arguments(Arguments, Singletons)
 1899    ->  Unread = [Occurrence | UnreadRest],
 1900        Read = ReadRest
 1901    ;   Read = [Occurrence | ReadRest],
 1902        Unread = UnreadRest
 1903    ),
 1904    occurrences_partitioned(Occurrences, Singletons, ReadRest, UnreadRest).
 1905
 1906singleton_arguments([], _).
 1907singleton_arguments([Argument | Arguments], Singletons) :-
 1908    var(Argument),
 1909    variable_member(Argument, Singletons),
 1910    singleton_arguments(Arguments, Singletons).
 1911
 1912unread_checks([], []).
 1913unread_checks([occurrence(id, Layer, Id, Handle) | Occurrences],
 1914              [(nonvar(Handle) -> true ; hash_consing:intern(Layer, Id)) | Checks]) :-
 1915    unread_checks(Occurrences, Checks).
 1916
 1917goal_scheduled([], Called, Called) :-
 1918    !.
 1919goal_scheduled(BottomUp, Called, (ground(Variables) -> Fast ; General)) :-
 1920    reverse(BottomUp, TopDown),
 1921    terms_variables(BottomUp, Variables),
 1922    occurrences_goal(BottomUp, related, Inserts),
 1923    occurrences_goal(BottomUp, represented_before_call, Settled),
 1924    occurrences_goal(TopDown, related, Finished),
 1925    conjoined([Inserts, Called], Fast),
 1926    conjoined([Settled, Called, Finished], General).
 terms_variables(+Occurrences, -Variables)
The variables of the occurrences' layers other than their own Handles.
 1932terms_variables(Occurrences, Variables) :-
 1933    occurrences_terms(Occurrences, Terms),
 1934    term_variables(Terms, All),
 1935    occurrences_handles(Occurrences, Handles),
 1936    variables_without(All, Handles, Variables).
 1937
 1938occurrences_terms([], []).
 1939occurrences_terms([occurrence(_, Layer, _, _) | Occurrences], [Layer | Terms]) :-
 1940    occurrences_terms(Occurrences, Terms).
 1941
 1942occurrences_handles([], []).
 1943occurrences_handles([occurrence(_, _, _, Handle) | Occurrences], [Handle | Handles]) :-
 1944    occurrences_handles(Occurrences, Handles).
 1945
 1946variables_without([], _, []).
 1947variables_without([Variable | Variables], Excluded, Kept) :-
 1948    (   variable_member(Variable, Excluded)
 1949    ->  Kept = Rest
 1950    ;   Kept = [Variable | Rest]
 1951    ),
 1952    variables_without(Variables, Excluded, Rest).
 listed_absent(+Term, +File, +Place)
No listed constructor occurs in Term, a place the rewrite does not schedule; raises otherwise.
 1959listed_absent(Term, File, Place) :-
 1960    (   listed_occurs(Term, File)
 1961    ->  throw(error(domain_error(term_without_rewritten_constructor, Term),
 1962                    context(hash_consing:rewritten/1, Place)))
 1963    ;   true
 1964    ).
 1965
 1966listed_occurs(Term, File) :-
 1967    compound(Term),
 1968    !,
 1969    compound_name_arity(Term, Name, Arity),
 1970    (   registered_constructor(File, Name, Arity)
 1971    ->  true
 1972    ;   arg(_, Term, Argument),
 1973        listed_occurs(Argument, File)
 1974    ).
 1975listed_occurs(Term, File) :-
 1976    atom(Term),
 1977    registered_constructor(File, Term, 0).
 1978
 1979%   ---- the hook, last, so that this file's own clauses are read before it
 1980%   is active ----
 1981
 1982:- multifile user:term_expansion/2. 1983:- dynamic user:term_expansion/2. 1984
 1985user:term_expansion(Source, Rewritten) :-
 1986    hash_consing:source_rewritten(Source, Rewritten).
 1987
 1988%   The message of a constructor with no templates names the two ways to
 1989%   give it some.
 1990
 1991:- multifile prolog:error_message//1. 1992
 1993prolog:error_message(resource_error(hash_consing_store)) -->
 1994    [ 'The hash_consing store passed the limit set by the flag'-[], nl,
 1995      'hash_consing_store_limit, or this thread passed the growth budget'-[], nl,
 1996      'set by hash_consing:store_growth_bounded/1; the store keeps every'-[], nl,
 1997      'term interned so far.'-[]
 1998    ].
 1999prolog:error_message(existence_error(templates, Name/Arity)) -->
 2000    [ 'No templates for the constructor ~q in this process.'-[Name/Arity], nl,
 2001      'Declare them with hash_consing:declared/1, or list them in the'-[], nl,
 2002      'hash_consing:rewritten/1 directive of the files that use them.'-[]
 2003    ]