The Automatic Barrier Solver (TTRD)
In raw Vulkan programming, managing synchronization is notoriously difficult. Developers must manually insert execution and memory barriers (vkCmdPipelineBarrier2), explicitly specifying pipeline stages and access masks to prevent data hazards.
Manual synchronization presents two major risks:
- Under-synchronization: Missing a barrier causes silent data corruption, screen tearing, and non-deterministic race conditions.
- Over-synchronization: Inserting redundant or overly broad barriers serializes the GPU execution units, creating pipeline bubbles that destroy performance.
Enki eliminates manual synchronization entirely. Through its Transitive Reduction Dependency Solver (TTRD), the runtime derives the mathematically optimal set of pipeline barriers automatically.
Note: If you are not interested in the underlying runtime implementation, you can safely ignore this section. This is an optional architectural overview.
1. Leveraging Rust Mutability Semantics
How does the runtime know what memory barriers are required without developer annotations?
Enki leverages the semantic guarantees already encoded in Rust’s type system:
- When a
#[nam]declares&Tor&[T], the argument is tagged internally asAccessIntent::Read. - When a
#[nam]declares&mut Tor&mut [T], the argument is tagged asAccessIntent::Write(orReadWrite).
Because each physical buffer allocation in VRAM possesses a unique internal engine slot ID, the runtime tracks the chronological memory access history for every resource across all tasks queued within an enki.flow.
2. Classical Data Hazards
As tasks are pushed to the active queue, the engine builds a directed dependency graph by detecting classical hazard conditions on each memory slot:
- RAW (Read-After-Write): Task
Awrites to a buffer; TaskBsubsequently reads from it. TaskBmust wait for TaskA’s writes to be made visible. - WAR (Write-After-Read): Task
Areads from a buffer; TaskBsubsequently writes to it. TaskBmust not overwrite memory before TaskAcompletes reading. - WAW (Write-After-Write): Task
Awrites to a buffer; TaskBsubsequently overwrites it. Write orders must be strictly preserved.
3. The Transitive Reduction Algorithm
In a multi-pass compute workflow, naive hazard tracking creates numerous redundant dependencies.
For example, consider three sequential tasks operating on the same buffer:
- Task
Awrites data (Write). - Task
Breads and updates data (Read / Write). - Task
Creads the final data (Read).
A naive dependency system inserts three barriers: A B, B C, and A C.
However, because Task B already depends on Task A, and Task C depends on Task B, the direct dependency from A C is mathematically redundant. Inserting a barrier for A C forces the GPU to stall unnecessarily.
Graph Reduction via Reachability
Enki applies graph-theoretic Transitive Reduction using depth-first search (DFS) reachability analysis:
If a directed path already exists between task
uand taskvthrough an intermediate taskw(utowtov), the direct edgeutovis pruned from the dependency graph.
After reduction, the remaining edges represent the minimum necessary and sufficient set of hardware barriers.