For contributors — how the frontend handles richer language constructs
internally. This is an architecture page, not a user guide.
- The frontend only accepts a richer construct if that construct has a finite representation the compiler can build or reject at compile time.
- Each accepted construct is normalized or lowered by its supported execution engine; none runs through a hidden source-language interpreter.
Pipeline Position
The deterministic frontend used byxlog run and inspected by xlog explain
begins with this common normalization sequence:
- parse source into the frontend AST (the parsed syntax tree) and merge imports;
- expand user-defined function calls in ordinary rule and integrity constraint bodies after imports are merged, using the entry program’s configured recursion limit;
- normalize safe meta builtins (see Safe Meta Subset);
- normalize finite list builtins (turn finite lists into ordinary relations);
- desugar supported shared-variable epistemic constraints before choosing an execution route.
- desugar queries and constraints;
- reapply meta normalization inside the compiler; this pass is idempotent;
- reapply list normalization inside the compiler; this pass is also idempotent;
- apply eligible magic-set rewrites (see Magic Sets);
- validate deterministic negation safety (see Deterministic Negation);
- stratify dependencies — sort the rules into layers so that negation is evaluated only after the relations it reads are fully computed;
- lower to RIR — xlog’s internal typed intermediate representation — and continue through the optimizer and runtime.
Finite Terms
The frontend accepts a richer term only when that term has a finite representation. The table below lists each accepted surface form and the role it plays.
Four kinds of construct are outside the contract and are not accepted:
open-ended generation, cyclic terms, runtime-variable predicate dispatch (choosing
which predicate to call from a value only known at runtime), and dynamic database
mutation.
Safe Meta Subset
The meta layer — constructs that talk about predicates rather than call them directly — is intentionally finite. Accepted meta predicates are rewritten into ordinary relations by themeta_normalize pass, which runs before list
normalization and before lowering.
Unsupported dynamic meta forms do not run. They fail at compile time with a
diagnostic, rather than being handed to a CPU-side meta engine and interpreted at
runtime.
Keeping the meta subset finite means accepted programs stay on the normal typed
path: the same AST, RIR, statistics, optimizer, and runtime as any other program.
Magic Sets
Magic-set rewriting is a query-optimization technique. It pushes the bound arguments of a recursive query into the recursion, so the program computes only the facts relevant to that query instead of the whole relation. The#pragma magic_sets = auto|on|off pragma controls this rewrite for the supported
deterministic subset. Like all pragmas it applies only when declared in the entry
file; a declaration in an imported module is ignored with warning[W0510] (see
the pragmas guide):
autorewrites only when the compiler can prove the query has a supported shape.onfails closed: if the requested rewrite is unsafe or unsupported, compilation stops with an error rather than falling back silently.offleaves the program on the ordinary recursive path, with no rewrite.
Deterministic Negation
Deterministicnot atom is stratified closed-world negation: not atom succeeds
when atom cannot be derived, and it is evaluated only after atom’s relation is
complete (that is the “stratified” part).
The compiler checks this in two steps. It validates source-order safety — that
every variable is bound before a negated goal uses it — after normalization and
before stratification. It then maps any unsafe cycles or binding errors to
deterministic-negation diagnostics.
This path is separate from probabilistic or epistemic semantics.
Probabilistic Aggregates
Finite probabilistic aggregate support is routed through thexlog-prob component.
There are two evaluation paths:
- Exact paths enumerate every finite aggregate outcome and record it into the provenance structures the runtime carries (internally, PIR).
- Monte Carlo paths approximate the result by sampling. They reuse the same aggregate operator semantics as ordinary deterministic aggregate execution.
Incremental Parsing And Explain
ParserSession and the incremental parser retain per-statement spans, parse-cache
statistics, and cache-invalidation data. These support developer workflows such as
re-parsing only the statements that changed.
The CLI explain path reuses the same parser and compiler surfaces. It reports:
- parse-cache and AST counts;
- stratification, relational compilation, optimizer, and WCOJ reporting status;
- magic-set rewrite and epistemic lowering status;
- aggregate lifting summaries, provenance, proof traces, and generated-row diagnostics;
- ignored-pragma warnings (
W0510) on stderr when imported modules declare pragmas, since pragmas apply only when declared in the entry file.
Construct Classification
The contract sorts every language construct by three properties:- the source syntax the parser accepts;
- the finite normalization or lowering path it takes;
- the typed failure mode used when the shape is unsupported.