Implementation Notes (flowlog.c)

This document summarizes the main internal components of the Flowlog engine in flowlog.c.

Navigation: flowlog.c uses SECTION NN and SUBSECTION NN.N headings. When you want to jump to the implementation of a topic described here, search for the corresponding section heading. The full section map lives in DEVELOPMENT.md.

Code references (section markers in flowlog.c):

Parser and program representation

The parser supports ISO term syntax, quoted atoms, lists, operators, and the ISO read_term/3 / write_term/3 option set used by tests.

Operator parsing follows a small disambiguation rule that matters for some control constructs (SUBSECTIONS 10.4/10.5):

For example, \+ (A -> B) parses as negation of an if-then goal, while \+(A -> B) is treated as a functor call and requires extra parentheses around A -> B.

Program loading is deterministic (SECTION 25):

Terms and environments

Flowlog represents terms as a tagged union (SUBSECTION 05.2):

Integers and arithmetic

Flowlog uses a two-tier integer representation to keep small arithmetic fast while supporting unbounded ISO integers:

This makes current_prolog_flag(bounded,false) true without penalizing common small-integer code paths. For the full layout and algorithm notes, see ARITHMETIC.md.

Bindings live in a persistent environment structure (pl_env) with parent links (copy-on-write style) so backtracking can cheaply fork environments (SECTION 14).

To keep variable dereferencing fast even with parent-linked environments, each clause-instantiation frame records the “introduced scope”, and variable lookup stops once that scope is reached (older frames cannot contain bindings for a scope that did not yet exist). The unifier also opportunistically caches deep ancestor bindings into the current env frame to reduce repeated parent-chain scans in pure recursive workloads (SECTION 14).

Flowlog also maintains a small amount of call metadata (predicate indicator, depth, flags) used for (SECTION 06):

Unification

Execution engines

Flowlog provides three execution engines that share the same parser, program representation, built-ins, error model, and indexing (SECTIONS 22/23/24). The default engine is wamvm (SECTION 25).

1) Interpreter (interp / tree)

The interpreter is a direct solver (closer to a “tree-walking” engine than a compilation pipeline) (SECTION 22):

This engine supports the full feature set, including (SECTIONS 13/20/14):

2) WAM-lite (wam)

The WAM-lite engine is a fast fallback/alternative engine and is intended to reduce per-goal overhead for common pure/static ISO code (SECTION 23).

In broad terms it:

The interpreter remains available as a reference engine and for debugging, but the WAM-lite engine aims to execute the full feature set directly (including dynamic database predicates, tabling, and occurs-check).

To detect what engine actually ran a query from within Prolog, use (SECTIONS 22/23/24):

?- current_prolog_flag(flowlog_engine_active, Engine).

Parallelism and the engines

3) WAM-VM (wamvm)

wamvm is the default engine mode and runs user-defined clause bodies on a small bytecode VM (see WAM_ROADMAP.md) (SECTIONS 18/24; default in SECTION 25).

In broad terms:

Performance notes:

Fallbacks:

Conformance note:

Predicate selection and indexing

Flowlog uses multiple indexing layers (SECTION 13):

Indexing is designed to be safe under dynamic database updates by using epoching and snapshots (SECTION 13).

Indexing is a performance feature, not a semantic one:

Dynamic database

ISO database predicates (including asserta/1, assertz/1, retract/1, abolish/1, clause/2) are implemented with (SECTION 13):

Tabling

Flowlog supports call-by-variant tabling for declared predicates (SECTION 20):

:- table(path/2).

Internally, each tabled predicate maintains:

Tabling runs under both engines. While tabling is active, OR-parallelism is currently disabled to keep the table state single-threaded (SECTION 20 and SECTION 21). Tabled predicates are also treated as AND-par unsafe (they can create observable search/answer effects) (SECTION 20 and SECTION 23).

OR-parallelism (choicepoints)

OR-parallelism runs independent alternative ranges concurrently (SECTION 21):

Cancellation:

I/O:

AND-parallelism (safe goal subsets)

AND-parallelism is used for limited “safe” cases (SECTION 23):

Flowlog performs a conservative analysis and only parallelizes when it can preserve ISO-visible semantics (SECTION 23).

REPL and terminal I/O

The REPL is implemented directly in flowlog.c (SECTION 26):