Implementation Notes (flowlog.c)

This document summarizes the main internal components of the Flowlog engine 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:

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:

Terms and environments

Flowlog represents terms as a tagged union:

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

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.

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

Unification

Execution engines

Flowlog provides three execution engines that share the same parser, program representation, built-ins, error model, and indexing. The default engine is wamvm.

1) Interpreter (interp / tree)

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

This engine supports the full feature set, including:

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.

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:

?- 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).

In broad terms:

Performance notes:

Fallbacks:

Conformance note:

Predicate selection and indexing

Flowlog uses multiple indexing layers:

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

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:

Tabling

Flowlog supports call-by-variant tabling for declared predicates:

:- 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. Tabled predicates are also treated as AND-par unsafe (they can create observable search/answer effects).

OR-parallelism (choicepoints)

OR-parallelism runs independent alternative ranges concurrently:

Cancellation:

I/O:

AND-parallelism (safe goal subsets)

AND-parallelism is used for limited “safe” cases:

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

REPL and terminal I/O

The REPL is implemented directly in flowlog.c: