Generic, no_std graph and dataflow framework for code intelligence, program analysis, decompilation, and compiler infrastructure.
cfglib has purpose-built storage for arbitrary directed relations, language-parametric scope graphs, file-incremental stack graphs, and Cfg<I, E = ()> graphs. A Cfg has basic blocks, typed control-flow edges, caller-owned edge metadata, and exception regions.
cfglib::ir holds IR traits. ir::rtl is the register-transfer contract. ir::mlil is the read-only view contract of a flat function in static single assignment form with block parameters. ir::hlil is the structured contract: a read-only view of statement and expression trees, a builder trait that the consumer implements, and the walks over the shapes. The consumer owns every IR storage, builder implementation, editing pass, and IR algorithm (lifting, lowering, verification, value analyses, and the facts over an RTL function). The RTL and MLIL views are rooted graph views, so the graph algorithms of cfglib run on them and return facts about the graph.
Small read-only node and edge view contracts let consumer-owned stores and zero-copy filtered views reuse the algorithms. CfgView extends them for the instruction-level analyses of a consumer, so the generic solvers run the same way over a consumer block store as over Cfg. Every instruction-adjacent axis is consumer-typed. Dataflow variables, constants, expression operators, side-effect vocabularies, branch targets, call targets, and edge payloads all come from the adapter. x86 registers and flags, shader register components, bytecode locals, compiler IR values, and source-language symbols do not need a library-owned numbering scheme. String literals and symbol ids flow through the solvers as naturally as machine words and addresses.
On top of that, cfglib has a compiler middle-end toolkit: dominator trees, dominance frontiers, and generic dataflow solvers. Instruction-level analyses such as reaching definitions, SSA construction, and memory SSA live in the il crate.
Named transformations compose through PassPipeline<T, E>. A pipeline keeps
the caller's declared order, supports closure-backed or stateful trait passes,
reports which executions changed the target, and attributes the first failure
while retaining the completed-prefix report. Pass selection and canonical
schedules remain consumer policy, so one framework can drive a CFG or any
consumer-owned compilation context without coupling their dialects.
Everything is no_std + alloc. The core graph store keeps adjacency in compressed arrays and removes entities with a liveness bit, so identities stay stable until an explicit compact().
No trait is required to build, verify, analyze, or render a CFG. Source frontends translate their syntax trees straight into blocks:
use cfglib::{Cfg, DominatorTree, EdgeKind, PostDominatorTree, verify};
let mut cfg = Cfg::<Stmt>::new();
let then_block = cfg.new_block();
let merge = cfg.new_block();
cfg.block_mut(cfg.entry()).push(stmt_if);
cfg.block_mut(then_block).push(stmt_assign);
cfg.add_edge(cfg.entry(), then_block, EdgeKind::ConditionalTrue);
cfg.add_edge(cfg.entry(), merge, EdgeKind::ConditionalFalse);
cfg.add_edge(then_block, merge, EdgeKind::Fallthrough);
assert!(verify(&cfg).is_ok());
let dominators = DominatorTree::compute(&cfg);
let post_dominators = PostDominatorTree::compute(&cfg);Frontends with a flat, structured stream (shader bytecode, structured ISAs)
implement FlowControl and use CfgBuilder; explicit gotos are wired
afterwards through the opt-in JumpTargets trait:
use cfglib::{CfgBuilder, FlowControl, FlowEffect, resolve_jump_edges};
impl FlowControl for Inst {
fn flow_effect(&self) -> FlowEffect {
match self.opcode {
Op::If => FlowEffect::ConditionalOpen,
Op::Else => FlowEffect::ConditionalAlternate,
Op::EndIf => FlowEffect::ConditionalClose,
Op::Loop => FlowEffect::LoopOpen,
Op::End => FlowEffect::LoopClose,
Op::Ret => FlowEffect::Return,
_ => FlowEffect::Fallthrough,
}
}
}
let mut cfg = CfgBuilder::build(instructions).unwrap();
let resolution = resolve_jump_edges(&mut cfg); // wires goto/label edgesGraph<N, E> is the single owned storage type for consumer-defined node and edge payloads, stable identities, and forward/reverse adjacency. GraphView lets existing graph stores use node algorithms without first migrating their storage; EdgeView additionally exposes stable edge identity, view-oriented endpoints, payloads, and ordered adjacency. FilteredEdges borrows either representation through an edge predicate without cloning or renumbering anything—for example, the same CFG can be viewed as normal-only flow or full normal-plus-exception flow. RootedView adds a distinguished entry node for algorithms that need one (dominance, reachability metrics, loop and interval analysis), and the Rooted adapter roots any plain view at a chosen node. This layer is suitable for symbol/reference graphs, value-flow graphs, call graphs, type relations, import graphs, grammar dependencies, and analysis-derived relations; it has no instruction or binary-analysis concepts.
use cfglib::{DominatorTree, Graph, Rooted, TraversalDirection, shortest_path};
let mut graph = Graph::new();
let source = graph.add_node("definition");
let target = graph.add_node("call result");
let edge = graph.add_edge(source, target, ("return", "src/lib.rs", 42));
assert_eq!(graph.edge(edge).payload().0, "return");
assert_eq!(
shortest_path(&graph, source, target, TraversalDirection::Outgoing),
Some(vec![source, target]),
);
let dominators = DominatorTree::compute(&Rooted::new(&graph, source));Graph keeps a compressed sparse-row base beside an appendable delta in one type: adding a node or an edge is constant time with no per-node allocation, removal clears a bit in a liveness bitset, out_degree/in_degree answer from the compressed run's two offsets plus a walk of whatever was appended since, redirecting one endpoint keeps the edge's identity, and compact() folds the delta back into the base and hands back a Renumbering describing every identity it moved. That is what makes whole-codebase graphs affordable: a node costs its payload and a bit, with no inline adjacency slack and no per-node allocation, and benches/graph-store.rs measures the build, the incremental appends, and the compaction at a million nodes and four million edges. The module documentation states the design and its costs; benches/graph-store.rs measures it.
Fanout<K, V>, DenseFanout<V, K>, and SortedMap<K, V> are that compressed base without the edges. They are built-once tables — from pairs or from grouped values, read-only afterwards — holding every key's run of values end to end in one array, keyed by a sparse ordered key, by a dense 0..bound index (any DenseId, so a tagged Id<T> keys one directly), or, in the single-value case, by a sparse key to one value. A reverse index from a symbol to its reference sites, a scope to the rows declared in it, or a file to its flow sites is the same shape as adjacency and should cost the same: three allocations for the whole table rather than one per key, get returning a slice, iter() walking keys ascending, and heap_bytes() reporting exactly what the columns hold.
Identities are tagged — Id<NodeTag>, Id<EdgeTag>, BlockId = Id<BlockTag>, ScopeId, StackNodeId — so a block identity and a symbol identity stay distinct types without a newtype and a pair of conversion shims for each. One trait, DenseId, covers them all, and a view reports two numbers rather than one: node_bound() is the exclusive upper bound that sizes a side table, and node_ids() yields the live nodes. A removed node is not a node of the view, so an analysis runs over a mutated store without compacting it first and without seeing a phantom singleton.
graph::scope::ScopeGraph<S, L, R, D, Q> separates language facts from
language policy. It stores consumer-defined scope metadata, labeled
scope-to-scope edges, relation-tagged declarations, and references. A ScopeGraphQuery
implementation supplies the path-language state machine, declaration matching,
label and data orders, optional indexed candidate visitation and decisive-match
pruning, and—when needed—a full path/data shadowing order. This keeps lexical parents, imports,
inheritance, namespaces, ordered declarations, accessibility, redirects, and
opacity in the frontend's vocabulary.
ScopeResolution::compute resolves a stored reference.
ScopeResolution::compute_from runs the same algorithm for an ephemeral query,
which supports completion and speculative refactoring without modifying the
graph. ScopeResolutionIndex resolves all stored references and provides the
reverse definition-to-reference relation used by rename and find-references.
Paths are cycle-free, deterministic, and optionally bounded; truncated results
report which bound was reached. Scope storage supports serde when the feature
is enabled.
ScopeFirstResolution::try_compute follows the one upward path that a
ScopeCursorQuery admits and stops at the first accepted datum. Its hooks see
a ScopeCursor (the start, the end, and the length of a path) instead of a
stored path, so a walk of D scopes costs O(D) hook calls and allocates
nothing. ScopeCursor::of_path lets one policy type serve both query forms.
graph::stack::StackGraph<F, S, N, E> implements the standard root,
exported/internal scope, symbol push/pop, scoped-symbol push/pop, drop-scopes,
and jump-to-scope nodes. Symbols, file records, node metadata, and edge metadata
are consumer types.
Concrete paths maintain symbol and scope stacks transactionally, scoped symbols
can pause one lookup while another resolves, and edge precedence removes
shadowed complete paths while retaining genuine ambiguity.
Every ordinary node belongs to one file partition. Direct cross-file edges are
rejected; independently constructed files compose through the root or exported
scope stitching endpoints. StackPartialPathSet extracts reusable structural
routes per file, StackPartialPathDatabase replaces one file at a time while
leaving other identities stable, and StackGraph::clear_file retires one
changed partition with node/edge tombstones before it is rebuilt. Then
StackResolution::compute_from_partials replays compatible summaries with the
same semantics as direct search. Graphs and partial-path databases are
serializable with serde, so unchanged file summaries can be persisted and
reused. Both direct and stitched indexes expose reverse
definition-to-reference bindings.
| Feature | Description |
|---|---|
Generic Cfg<I, E = ()> |
Parameterised over any instruction and caller-owned edge payload; unit payload preserves the original API |
no_std + alloc |
Runs in embedded, kernel, and WASM environments |
CfgBuilder |
Builds a CFG from a flat structured instruction stream (if/else/endif, loop/endloop, switch/case/endswitch, break, continue) |
| Goto wiring | resolve_jump_edges + JumpTargets (consumer-typed targets: labels, addresses, syntax nodes) |
| Compressed adjacency | Cfg stores its blocks and edges in Graph: a compressed base plus an appendable delta, with no per-block adjacency allocation |
| Edge removal | remove_edge() clears the liveness bit of the edge; existing EdgeIds stay stable until compact() renumbers the survivors |
| Edge metadata | Stable EdgeId, endpoints, EdgeKind, optional weight, and a caller-owned payload; ExceptionFlow<M> standardizes search/unwind phase and execute/search/continue disposition while retaining platform metadata |
| Regions | Try/catch/finally regions with explicit HandlerBody::Known/Unknown completeness and HandlerKind (Catch, CatchAll, Finally, Fault, Filter); install_clr_region and install_seh_region import normalized platform clauses |
| Cleanup continuations | add_continuation / set_cleanup_resume → Cleanup (Continuation + CompletionReason): which route out of a single shared finally block a resume edge belongs to |
| Handler metadata | HandlerMetadata<M> side table (HandlerFilters<F> / HandlerTypes<T>) — consumer-typed filter predicates and CLR caught-type tokens keyed by HandlerRef, with no type parameter on Cfg |
| Native handler state | SehRegistrationChain<F, H> models per-thread x86 frame registrations; VehModel<H> separately models ordered process-wide vectored exception/continue handlers |
| Subgraph extraction | subgraph() or subgraph_mapped() with dense O(1) block-id remapping and payload cloning |
| Block splitting | split_block(), mapped payload-aware variants, and validated multi-point splitting with automatic stable edge transfer |
serde feature |
Optional serialization support |
| Leader-based construction | build_address_cfg with AddressCfgOptions / CallPolicy, the AddressInstruction / AddressSpace traits, Flow, and AddressHandler. Machine/bytecode streams: caller-selected function entry; calls kept as edges or flattened into reported call sites; leaders at targets, after terminators, at exceptional instructions, and at exception boundaries; known successors retained beside explicit unresolved transfers; typed normal, instruction-exceptional, and unwind edges through a consumer payload hook, every kind chosen by EdgeRole::kind; nested regions registered enclosing-first with parents wired |
| Exact unwind routes | build_address_cfg_with_routes with UnwindRoute, then AddressGraph::add_unwind_routes: the caller states the ways that the runtime takes when a protected instruction throws (funclets in order, opaque calls, and a catch handler, a resume or a propagation as the end). The graph gets one EdgeRole::Route edge for each step of each way, and one instance of a funclet for each distinct way on from it, so a shared funclet never leads into the next step of another way. The result lists the hops with the opaque calls of each way |
| Given normal edges | build_address_cfg_from_given_edges with GivenEdges: a recovery that already decided the control flow gives the normal edges of each instruction that ends one of its paths, each with its kind and payload. The graph takes no normal edge from the Flow of such an instruction, so it can hold a continuation that the flow lacks (the return of a trap) and leave out one that the flow has (the continuation of a call that never returns). The builder does not check a given kind against the flow. Every other instruction continues at its end address, and a missing continuation is an error, not a silent drop. A call without given edges keeps the edges of its flow, so the builder cannot detect a call that never returns and has no given edges. Handler regions and unwind routes work as with build_address_cfg_with_routes |
cfglib::ir holds IR traits. It has no IR storage, no builder implementation, no editing code, and no algorithm that reads the operands, values, or effects of instructions. The consumer owns the dense arenas, builds its own records, applies every change, and owns its IR algorithms. The graph algorithms of cfglib read the RTL and MLIL views as graphs and return facts.
A fact is a side table about one state of a function. No algorithm changes the function that it reads. After an edit, the consumer computes the facts again. cfglib stores no fact inside a function.
| Level | Module | The consumer implements | cfglib provides |
|---|---|---|---|
| Register transfer (RTL) | ir::rtl |
Dialect, InstructionBuilder, RecordView, FunctionView, Fold |
Nothing. The facts over an RTL function belong to the consumer. |
| Medium level (MLIL) | ir::mlil |
Dialect, FunctionView |
Nothing. Value analyses over the view belong to the consumer. |
| Structured (HLIL) | ir::hlil |
Dialect, FunctionView, FunctionBuilder |
The walks over the shapes |
Every dialect type is plain data that the consumer defines. The traits contain no language, runtime, calling convention, opcode, or source-location type.
cfglib no longer holds IR storage or IR algorithms. The removal covers the RTL, MLIL, and HLIL function types and builder implementations, tail duplication, the shared vocabulary trait, the provenance map, the facts over an RTL function (root table, block summaries, and the rest, now in il::rtl::facts), and the value analyses over MLIL (use index, liveness, constant propagation, and dead-instruction marking). A consumer that needs one of them keeps it in its own crate; the il crate holds them for its IR.
One machine instruction is one atomic state update:
- Every read sees the single state before the instruction.
- Pure computation, loads, stores, and intrinsics follow in a stated order.
- Writes collect into one delta. The delta commits at the instruction boundary.
- One control boundary ends the instruction.
A fault is a typed outcome that an effect or a raise declares. It is not a control-flow successor. A raised outcome commits nothing of the delta. A raise can publish state of its own, its bindings: some architectures set state before they raise, such as the status flags of a floating-point exception that the program does not mask. An instruction has no control flow of its own. A repeated instruction branches to its own address, and a choice inside one instruction is a select or a guarded write.
Every RTL value is a signless bit vector. Signedness, a floating format, and a lane structure are properties of an operator, not of a value.
| Item | Description |
|---|---|
Bits, BitSlice |
Bits is the one value type: a width in 1..=Bits::MAX_WIDTH (8192). Bits::BOOL is the one-bit type. A BitSlice is a bit range inside one root. |
Dialect |
The vocabulary of one instruction set, normally a unit marker type. The associated types are Root, Operator, EffectOp, WritePolicy, Outcome, and NondetDomain. Dialect::root_width gives the width of a root. Roots never alias: a register view is a BitSlice of one root. Place<D> names such a range. |
InstructionBuilder<D> |
The write-only protocol through which instruction semantics emit one record into consumer storage. The producer calls read, constant, apply, nondet, effect, raise, and write in statement order, then finish with a Control. finish seals the record. The protocol cannot read a statement back or change it. |
RecordView<D> |
One sealed record: its statements, its control, and the width of each value. A sealed record is read-only. |
FunctionView<D> |
A RootedView over the decoded machine control flow. The root is the entry block. It adds inst_bound, value_bound, the instructions of a block, and the record of an instruction. |
Fold |
Constant evaluation of the operators of a dialect. Fold::fold writes the results of an operator over ConstBits operands to a buffer. It returns false when the operator has no constant evaluation for the operands. |
The builder takes these plain-data types, and a record yields them. Every slice inside them is flat, so a consumer keeps them in dense pools:
Selectionsays where a read takes its bits:Direct(onePlace),FiniteIndexed(the candidate that an index value picks), orSparseIndexed(an element of a sparse bank, such as a model-specific register).WriteTargetsays what a write changes:Root,FiniteIndexed, orSparseIndexed. ARootwrite carries a complete coverage plan: ascendingSpanActionvalues that start at bit zero, end at the root width, and leave no gap. Each span has anAction:PreservePreState,TakeInput, orNondeterministic.Raisenames an outcome, an optional one-bit guard, a payload, and its bindings. A raised outcome publishes no state of the delta. A binding pairs a root with the new value of the whole root. The value comes from the state before the instruction, like every other value. A binding is not a write: on the normal path the root keeps its value.Controlends the instruction:Advance,Jump,Branch,Call,Return,Dispatch, orTerminating. ADispatchnames a scrutinee. The case values belong to the decoded control flow, not to the record.Statementis one statement of a sealed record:Read,Constant,Apply,Nondet,Effect,Raise, orWrite. AValueRangeis a run of consecutive results.
InstructionBuilder states a contract. The consumer checks it when finish seals the record, and reports a violation through InstructionBuilder::Error:
- Every operand is a handle that the builder returned earlier.
- A
WriteTarget::Rootplan covers its whole root. Two writes of one instruction never cover the same bit. - A guard or condition has type
Bits::BOOL. - An unconditional
Raiseis followed byControl::Terminating, and no statement follows it. The record holds no write: the delta of such a record never commits. - The bindings of one
Raisename each root once. Each bound value has the width of its root.
An MLIL function is flat and in static single assignment form:
- Every instruction defines zero or more values and reads values by handle.
- There is no expression tree and no mutable variable.
- A block receives its join values as ordered parameters. The parameters of the entry block are the parameters of the function.
- The terminator of a block owns its successor records. A record holds the target block, the values that pass to the parameters of the target, and the edge metadata.
- The function never stores predecessors. They come from the successor records.
| Item | Description |
|---|---|
Dialect |
The vocabulary of one medium-level dialect. The associated types are Op (an operation with its immediate attributes), Type (the type of a value), and Edge (the metadata of a successor record, such as the case value of a switch arm). |
FunctionView<D> |
A RootedView over the blocks. The methods params, insts, operands, and results return slices, so the consumer keeps them in dense pools. The view also gives op, successors, block_of, def, ty, inst_bound, and value_bound. |
ValueDef, Successor |
ValueDef says where a value is defined: Result of an instruction or Param of a block. Successor is one successor record: target, args, and edge. |
A FunctionView follows these rules:
- Each live block ends with exactly one terminator, which is the last instruction of
insts. Only a terminator has successors. - Every value has exactly one definition, which
defreports. - The arguments of each successor record match the parameters of the target block in count and type.
- The adjacency of the
RootedViewagrees withsuccessors. - An identity below a bound that the view does not yield is a removed entity.
ir::mlil has no analysis of its own. A consumer that needs use lists, liveness, constant propagation, or dead-instruction marking builds them over FunctionView. The dialect of the consumer owns the semantics that those analyses need, such as effects and constant folding.
An HLIL function is a pair of trees over declared variables:
- Statements form trees: assignments,
if,while,do/while,loop,for,switch,breakandcontinue(optionally labeled),return, labeled groups withgoto,trywith handlers and afinallybody, and a dialectRegionthat wraps a body. - Values are typed expression trees of variable reads, dialect constants, and dialect operations.
- The statement and expression shapes (
StmtShape,ExprShape) are universal and belong to the library. A dialect supplies the operations, constants, value types, and variable metadata. - A variable holds differently typed values at different points, so each occurrence carries its own type.
| Item | Description |
|---|---|
Dialect, FunctionView<D> |
The vocabulary of one structured dialect, and the read-only view that lends statements, expressions, switch arms, and handlers as slices from the consumer's dense storage. |
FunctionBuilder<D> |
The write-only protocol that builds a function, children before parents. The consumer seals the function in finish. |
Entity, ChildBodyKind, StmtShape, ExprShape, SwitchArm, Handler, HandlerKind |
The shapes that the view lends, the role of a child body, and the name of one statement, expression, or variable. |
for_each_child_body, for_each_statement_expression, visit_statements, visit_expression_tree, expressions_equal, compound_assignment |
The walks over the shapes. for_each_child_body, visit_statements, and visit_expression_tree keep their own stack, so memory bounds the depth, not the thread's stack. |
A function follows these rules:
- Every statement and expression has exactly one parent, and a child has a lower index than its parent.
- A
continuewith a label targets aLabeledgroup whose body is exactly one loop, and abreakorgotocan target any label. - Control that completes must end in a
returnor iterate forever. - A region's operands are evaluated once, on entry, and the end of the region releases the values they had then.
The algorithms over this contract (lift from a flat function, lowering, structure recovery, verification, pseudocode, and declaration placement) belong to the consumers of the contract, not to cfglib.
| Algorithm | Function / Type | Description |
|---|---|---|
| DFS / BFS | depth_first_preorder, depth_first_postorder, breadth_first, and matching Cfg methods |
Direction-selectable traversals over GraphView |
| Edge-aware traversal | breadth_first_edges[_with], depth_first_edges[_with], shortest_path_edges, EdgeStep |
Every distinct edge once, with identity and endpoints, over any edge view; parallel edges stay distinct |
| Configurable search | search + SearchConfig (order, visited policy, direction, depth bound) |
First-match, pruning (Visit::Skip), early exit (ControlFlow::Break), and backtracking as configuration; VisitedPolicy::Path un-marks on unwind so every route to a node is reported |
| Reusable search marks | search_with_marks + EpochMarks (mark, is_marked, reset, grow_to) |
The same search with its visited marks in a caller-owned epoch-stamped buffer: a per-root pass allocates marks once instead of an O(node count) buffer per root, and each search still starts from a clean set (epoch bump, O(1)). A hand-written walk marks and resets the same buffer directly |
| Readable dense set | EpochSet |
The same epoch stamp with its members kept: constant-time insert/contains, a constant-time clear, and ones in one pass over the answer rather than the universe (insertion order, not ascending) |
| Reusable search scratch | search_with_scratch + SearchScratch |
The same search with the marks and the call's own buffers — seeds, frontier, adjacency — caller-owned, for a pass whose searches are small enough that the call is the cost; marks and buffers both reset on entry |
| Disjoint sets | DisjointSet (union by rank, union_toward_min for deterministic minimum representatives, growable via push, reset to reuse the storage) |
A union-find, usable directly by consumers |
| Open post-order fold | open_fold_post_order + OpenFold trait (FoldEnter::Leaf/Fold, MarkScope::Shared/Isolated) |
Child-answers-to-parent folding over a lazily discovered space: caller-keyed cycle guard (nodes need no Ord), per-node mark scoping (exact per-path marking or persistent pruning), early exit, depth bound — C++-style member lookup and type-algebra evaluators without hand-written frame stacks |
| Forward-only view support | scan_predecessors |
The honest O(nodes × edges) predecessors for views that store only successors, replacing per-consumer scan stubs |
| Traversal events | breadth_first_events → BfsEvent; depth_first_events → DfsEvent |
Breadth-first discovery with tree/non-tree edges, or depth-first discover/tree/back/forward-or-cross/finish events in pinned order |
| Open-graph search | open_search + OpenSearchConfig |
The same disciplines over a lazily discovered node space: successors come from a closure, no dense ids (import/re-export chases, ordered emission walks) |
| Open-graph events | open_breadth_first_events → OpenBfsEvent; open_depth_first_events → OpenDfsEvent |
Breadth-first discovery/refusal events or depth-first discover/finish/refusal events over a lazily discovered node space; path policy can revisit a shared node once per route |
| Open-graph route enumeration | open_breadth_first_paths → OpenPathsEvent |
Simple routes shortest-first over a lazily discovered space — no global marks, per-route cycle guard; exponential worst case, so bound with depth, Skip, or Break |
| Alias chase | follow, follow_path |
Out-degree ≤ 1 chase with a hop bound and a full-path cycle guard; the chain, or just its end |
| Shortest path | shortest_path (nodes), shortest_path_edges (edge witness) |
Forward or reverse unweighted witness path |
| Minimum-label relaxation | min_label_relaxation |
Edge-defined label transfer to a minimum fixpoint; nodes re-expand when their label improves |
| Multi-source reachability | reachable |
Dense Vec<bool> from a seed set, forward or reverse; order-insensitive |
| Nearest common ancestor | nearest_common_ancestor |
Bidirectional BFS meet of two nodes; smallest combined distance, ties by smallest node id |
| All common ancestors | common_ancestors → Vec<CommonAncestor> |
Every shared node with both hop counts, depth-bounded, in b's BFS discovery order — the consumer-rankable form (MRO linearization, overload preference) |
| Horn-clause derivability | HornClauses |
AND-OR closure (head <- b1 & b2): nullability, all-arguments-constant, all-callers-dead |
| Topological sort | topological_sort |
Stable ordering or cycle detection |
| Dominator tree | DominatorTree::compute (rooted views), compute_from (explicit root) |
Simple Lengauer-Tarjan over any graph view, O(m log n) with explicit stacks. The tree is numbered once in preorder, so dominates is O(1) and subtree, preorder, level, children, and nearest_common_dominator need no extra tables |
| Reusable dominator scratch | DominatorTree::compute_in, compute_from_in + DominatorScratch |
The same tree with the call's working buffers caller-owned, for a pass computing one tree per procedure over a codebase; the scratch holds dense indices, so one instance serves every graph and node-id type |
| Dominator forest | DominatorTree::complete_forest_in + DominatorScratch |
Extends a tree over the code its root misses: each source component of unreachable code roots a tree of its own, and every frontier query reads the forest like a tree. None, at the cost of one scan, when the root reaches every node |
| Post-dominator tree | PostDominatorTree::compute (rooted views) → PostDominatorTree<N>, PostNode |
Over a virtual exit that every exit joins. Exit-free regions are cut at the blocks closing each loop, so every reachable block has facts independent of successor order. post_dominates is O(1); ipdom, nearest_common_post_dominator, exits, cut_roots, reaches_exit, subtree, children, frontier (the control-dependence sources of a block), and control_dependents (the blocks control dependent on one arm) answer from the one numbered tree |
| Reusable post-dominator scratch | PostDominatorTree::compute_in + PostDominatorScratch |
The same tree with the call's working buffers caller-owned, parallel to DominatorScratch |
| Dominance frontiers | DominanceFrontiers::compute, recompute (into the same storage); frontier → &[N]; iterated_frontier + FrontierScratch |
Every block's frontier in one flat run table, read back as a slice in ascending order. iterated_frontier closes a set of definitions under the table, with an optional live-in filter, for SSA φ-placement with many queries per graph; DominatorTree::iterated_frontier answers the same query without a table, for a few queries |
| Dominator recompute diff | DominatorTree::compute_with_diff |
Recompute after a graph edit, reporting the nodes whose idom changed |
| Strongly connected components | tarjan_scc → SccDecomposition<N>, condensation → component DAG of owned Scc<N> payloads |
Generic iterative Tarjan algorithm, reverse-topological order (leaves first); components are ascending slices of one flat array (components(), component(node) → Scc with nodes(), contains, is_singleton, is_cyclic); removed node slots belong to no component, and querying one panics |
| SCC in topological order | kosaraju_scc → SccDecomposition<N> |
The same partition numbered sources first (index(u) < index(v) across every edge); the classic deterministic two-pass algorithm, for budgeted forward closures over the condensation |
| Condensation of a given decomposition | condensation_of(graph, &SccDecomposition) → Graph<(), ()> |
The component DAG whose node index is the given decomposition's component index, either algorithm's; deduplicated edges, and in-degrees plus dependents straight off the graph (the one-pass fixpoint shape) |
| Back-edge detection | find_back_edges (dominance, any view), find_back_edges_tagged (CFG, honors Back tags) |
Edges out of code the root reaches; unreachable code has no back-edges |
| Natural loop detection | detect_loops / detect_loops_tagged → Vec<NaturalLoop<N>> |
Header, body, latches, nesting depth; outermost loops first. A tagged loop on an irreducible graph covers the cycle, not the blocks before it. Each loop owns a set of its blocks, so loops nested n deep hold n²/2 entries between them: for deep nests ask the forest instead |
| Loop forest | LoopForest::compute / compute_tagged → loop_of_header, parent, depth, latches, contains, encloses, innermost, enclosing, is_nested, body, node_depths, natural_loops |
The same loops in near-linear space and time, whatever the nesting: every block is stored once under its innermost loop, and contains and encloses are constant time for every loop dominance finds. A tagged loop whose header does not dominate a latch (an irreducible cycle) keeps an explicit set of blocks, costs its own size, and is answered from that set; encloses and enclosing are exact where parent holds only the header. natural_loops materializes what detect_loops* return, and CfgMetrics and cfg_block_nesting_depths read the forest directly |
| Loop nesting tree | LoopNestingTree::compute |
Parent/child loop hierarchy, and the one statement of nesting depth: NaturalLoop::depth is the depth in this tree. Loops over the same body are siblings |
| Coldness classes | ColdnessClasses::compute over a ColdnessGraph → class, record (ColdClass: Never < Rare < Normal) |
The greatest assignment of three classes under a seed per node, a cap per successor record, and the rule that a node is no hotter than the hottest record it can take or that can reach it. O(n + m) with per-node counters and an explicit queue; independent of node and record order. The graph supplies the records in both directions, so a consumer serves it from its own adjacency tables |
| Control dependence graph | control_dependence_graph → Graph<N, ()> |
From post-dominator tree, over any view; a block that branches back to itself has the edge to itself |
| Interval analysis | IntervalAnalysis::compute → intervals |
First-level Allen-Cocke interval partition of the code the root reaches; use is_reducible for the reducibility test |
| Reducibility | is_reducible (exact test over a dominator tree), make_reducible (CFG node splitting) |
Irreducible cycles are split until every retreating edge is a back edge |
| Reverse CFG | reverse_cfg |
Flip all edges, swap entry/exits |
| Call graph | call_graph + CallInfo, propagate_summaries (callee-first SCC fixpoint) |
Consumer-typed callees; interprocedural summary scaffold |
| CFG diff | CfgDiff::compute |
Structural comparison (bindiff-style): blocks are matched by wiring, from the entries along edges of equal kind, over every live block; a matched block reports changed instruction counts and changed edges. Any edge payload, no trait bounds |
| Exception handling model | EhModel::compute |
Payload-generic CFG input; stable source EdgeId, exact handler/unwind/leave/resume/continue kinds, landing pads, cleanup/resume blocks, handler identities, protected-by mapping, and cleanup continuations |
| Integrity verification | verify, verify_view, verify_edge_view; verify_with + SemanticValidator |
Structural node/edge-view checks plus deterministic typed consumer hooks for cardinality, ordering, and edge-payload rules |
| DOT export | write_dot / to_dot + DotStyle (any node- and edge-bearing view); Cfg::to_dot (DisplayInstr), Cfg::to_dot_with (bound-free), Graph::to_dot |
One writer; the style supplies node labels, edge attributes, graph name, and layout direction. Labels are escaped and left-justified; the graph name, node prefix, color and style are quoted when DOT requires it, and a NaN or infinite pen width is not written |
| Text form | write_text / to_text + TextStyle, parse_text; Cfg::to_text, parse_cfg_text |
One line per node and per edge, ids are the dense indices. Round trips: write → parse → write is a fixed point, so two graphs compare as text |
Instruction-level analyses read the graph through CfgView. The graph-shaped solvers read a rooted, node, or edge view instead.
CfgView is the one read-only trait that the instruction-level algorithms of a consumer, and the CFG analyses of cfglib, take. It joins the graph views with the facts that these algorithms need and a graph view does not give: the ordered instructions of each block, the EdgeKind of each edge, and the optional branch weight of each edge.
| Part | Description |
|---|---|
| Supertraits | RootedView<NodeId = BlockId> and EdgeView<EdgeId = EdgeId>. The edge data implements KindedEdge. The root is the entry block. |
Inst, instructions |
The instruction type of the blocks, and the instructions of one block in program order. |
edge_weight |
The branch probability of an edge, in 0.0..=1.0 relative to the other edges leaving its source. The default is None, which means that the view has no profile. |
Cfg implements CfgView, so every caller that holds a Cfg keeps working. A consumer with its own block and edge storage implements the trait and runs the same analyses without a conversion. The unit tests of the trait run the block fixpoint solver on a vector-backed view and compare the answer with the one on a Cfg.
The contract has two rules. instructions accepts every identity that node_ids yields, and it returns the same slice each time. The index of an instruction in that slice is the instruction index of the ProgramPoint that an analysis reports.
The view has no separate exception model. An edge of kind EdgeKind::ExceptionUnwind leaves its source block before a throwing instruction. InstrInfo::may_unwind marks the throwing instructions of a block.
These analyses take &G where G: CfgView: CfgMetrics and detect_cfg_patterns, and every Problem solved by solve_problem. A transform that edits the graph still takes &mut Cfg. Live variables, reaching definitions, SSA construction, memory SSA, and memory value flow are in the il crate.
| Analysis | Function / Type | Description |
|---|---|---|
| Generic fixpoint solver | solve_problem[_from][_with_config], Problem / TryProblem traits, Facts |
Forward or backward, any lattice type; the block, node, and edge solvers carry the same seeded/bounded/fallible matrix over shared SolveConfig and SolveError, run on one worklist, and report node facts in one program-order Facts type (a backward node's IN is what its transfer produced); every problem names its starting fact boundary; meet_options lifts any lattice to Option<F> with None as unreachable bottom |
| Node-level fixpoint | solve_node_problem[_from][_with_config], NodeProblem / TryNodeProblem traits |
Per-node Facts over any graph view (taint, reachability-with-facts) |
| Slot fixpoint drivers | wave_fixpoint (WaveExecutor, SequentialExecutor, WaveRequeue), ordered_fixpoint (SlotAccess), SlotFixpointStats |
Schedule units over slots that the caller owns, without a graph view or a problem trait: waves evaluated against the facts known before the wave and merged in group order, or ordered (Gauss-Seidel) sweeps whose evaluations record the slots that they read |
| Edge-sensitive fixpoint | solve_edge_problem[_from][_with_config], EdgeProblem trait |
Full or seeded, optionally bounded, per-edge transfer over any edge view; stable id/data plus physical node pre/post states and deterministic bounded-solve errors |
| Fallible edge-sensitive fixpoint | try_solve_edge_problem[_from][_with_config], TryEdgeProblem trait |
Preserves consumer boundary, merge, node-transfer, and edge-transfer errors separately from solver limits |
| Reachability-lifted edge analysis | Reachable + ReachableEdgeProblem trait |
Verification-style analyses over Option facts: None is unreached bottom, transfers short-circuit, entry facts start the flow, and each edge chooses pre- or post-state (exceptional edges observe the state the node received) |
| Dense bit-set facts | DenseBits |
Set-of-indices lattice element packed 64 per word; union_with is the join and reports change for convergence checks; remove and clear clear one index or the whole row, words exposes the packed row, and ones/intersection step one set index at a time rather than one universe index |
| Analysis | Function / Type | Description |
|---|---|---|
| Exclusive handler extents | recover_exclusive_extents[_with], promote_exclusive_extents |
Evidence-reporting sibling of promote_handler_extents: blocks exclusively reachable from one handler entry along normal edges, boundary and ambiguity issues, and atomic per-region promotion that refuses overlap |
| Metrics | GraphMetrics::compute (any rooted view); CfgMetrics::compute |
Live node and edge counts (removed slots are not nodes), cyclomatic complexity, nesting depth (0 for no loops, 1 for one loop), instruction density |
| Pattern detection | detect_patterns (any view), detect_cfg_patterns (adds trampolines + arm orientation) |
Diamond, chain, self-loop, empty trampoline |
| Control tree | ControlTree::compute, compute_with_report (→ LiftReport), compute_with_report_treating_empty |
The structured form of a Cfg: if/else, pre-tested, post-tested, and endless loops, switch with case edges and a default, try/catch over declared handler extents, break/continue (labeled across loops), and goto/label. A payload-free fact like DominatorTree: a flat arena of ControlNodes that name BlockId, EdgeId, RegionId, and BlockSpan instruction ranges, so it holds no instruction and never borrows the graph. Merge points are immediate post-dominators; an exit-free region has none. Beside an arm that throws or returns, the arms that complete rejoin where they meet: before the stop of the walk that holds the branch, or, in a walk without one, before the exits that two or more arms reach. No owned code is dropped — the blocks the entry reaches and the blocks a handler owns, a catch pad no call unwinds into and its extent among them; a protected block the entry cannot reach is dead code — and LiftReport lists every goto, swept block, unstructured region, and dangling label. Structuring keeps its own stacks, so the nesting depth of an input is bounded by memory, not by the call stack |
| Control tree traversal | ControlTree::preorder, walk (ControlVisitor), child_bodies, for_each_instruction, write_pseudocode / to_pseudocode |
Non-recursive preorder and enter/leave walks, the child bodies of any node in execution order, every instruction in program-structure order, and indented pseudocode whose instruction text the caller supplies (the printer keeps its own stack too) |
PassPipeline composes heterogeneous named passes over any caller-owned target.
pass_fn adapts a closure; implementing Pass supports stateful reusable
passes. Schedules retain insertion order and report the completed prefix when a
fallible pass stops execution.
| Transform | Function | Description |
|---|---|---|
| Simplify (all-in-one) | simplify, simplify_mapped |
Unreachable removal + block merging + empty bypass until stable; mapped form composes identity changes |
| Remove unreachable | remove_unreachable |
Reachability pruning; blocks named by exception metadata are kept |
| Merge blocks | merge_blocks |
Coalesce single-succ/single-pred chains inside one protected region |
| Remove empty blocks | remove_empty_blocks |
Bypass empty fallthrough blocks that no handler or cleanup names |
| Critical edge splitting | split_critical_edges, split_critical_edges_with |
Insert blocks on multi-succ → multi-pred edges while retaining the original edge identity/payload and mapping both halves |
| Edge contraction | contract_edge, contract_edge_mapped |
Merge two blocks connected by a single edge; mapped form preserves surviving edge identities/payloads. Refuses exception edges and merges across a protection boundary |
| Node splitting | Cfg::split_block, Cfg::split_block_at_points[_with_payloads] |
Split at one or several validated consumer-selected instruction boundaries; the new blocks stay in the original's regions |
| Linearization | linearize, Emitter trait, BlockOrder, LinearizeError |
Re-serialize CFG to a flat stream; emitters speak BlockId, naming is theirs. Exceptional, call, and dispatch edges emit nothing; a conditional edge out of an empty block is an error |
| Width relaxation | relax_layout |
The assembler fixed point for symbolic layouts: monotone branch widening against caller-resolved labels, offset-dependent (alignment) widths, converged offsets plus the final label context |
The structural passes (simplify, merge_blocks, remove_empty_blocks,
remove_unreachable, contract_edge) keep a CFG's exception metadata true, so
Cfg::compact and EhModel stay usable after any sequence of them. A block a
handler, filter, or cleanup names by a single reference is never removed or
merged away (remove_unreachable treats such blocks as roots), and two blocks
merge only when the same regions protect both. Cfg::split_block is the
inverse: the new tail joins every region and handler body of the original.
Call Cfg::remove_unreachable_regions first to drop regions nothing can raise
into together with their handler blocks.
Every renderer writes into a &mut dyn fmt::Write sink. The to_* forms are the
allocating counterparts. Cfg::write_text indents through IndentedWriter,
which consumer printers can also use.
| Form | Entry points | Notes |
|---|---|---|
| DOT | write_dot / to_dot over a DotStyle; Cfg::write_dot, Cfg::write_dot_with, Graph::write_dot |
DotStyle carries the node-label hook, the edge-attribute hook (DotEdgeAttributes: label, color, style, penwidth), the graph name, the node-identifier prefix, and DotRankDir. control_flow_edge_attributes is the CFG's kind-to-color mapping; plain_edge_attributes decorates nothing |
| Text | write_text / to_text over a TextStyle, read back by parse_text; Cfg::write_text / Cfg::to_text, read back by parse_cfg_text |
One fact per line. n3 label, n3 -> n5 label, # comments; a CFG adds entry bb0, bb3: headers with indented instructions, bb3 -> bb5 kind, and region / handler lines. Escapes are \\, \n, \r, and, on the first character only, \-, \#, and \u{20}-style for whitespace. A document may leave at most 2^20 removed slots (TextErrorKind::TooManyHoles). Edges replay in identity order, so the adjacency order a redirect produced is only in the serde form |
Both label hooks are ordinary closures or function items of type
Label<Id, Data>; no_label and display_label cover the common answers,
and bind_label / bind_edge_attributes state the higher-ranked signature
for a closure that would otherwise infer one fixed payload lifetime.
The text form is a readable projection — identity, topology, and label text.
Edge weights, non-text consumer payloads, and cleanup records live in the
serde representation of the store, which remains the fidelity path.
The generic graph has no consumer trait requirement when it owns the storage. Graph, Cfg, and KeyedGraph expose both node and edge views directly. Implement DenseId and the four required GraphView methods (node_bound, node_ids, successors, and predecessors) only when adapting another graph store; implement EdgeView when algorithms must observe stable edge identities or payloads; add RootedView (or use Rooted) for entry-requiring algorithms. Implement CfgView when the instruction-level analyses must read your own block and edge storage. Instruction traits are opt-in according to which CFG and dataflow features an adapter needs — every associated type is the consumer's own:
Graph<N, E> (owned arbitrary graph; no adapter trait)
GraphView (existing consumer-owned graph storage)
├── NodeView (graph-owned node payloads)
├── EdgeView (stable edge identity, endpoints, data, adjacency)
└── RootedView (adds a distinguished entry node; `Rooted` adapts)
CfgView (RootedView + EdgeView + block instruction slices;
`Cfg` implements it; every instruction-level
analysis reads it — associated Inst)
FlowControl (required only by CfgBuilder)
└── JumpTargets (goto/label wiring — associated Target)
InstrInfo<Variable = V> (optional — native IR variables, defs/uses)
├── EffectInfo (associated Effect)
└── MemoryEventInfo (locations, address uses, access kinds, and fences — associated Location, Fence)
DisplayInstr (rendering only — DOT and text)
CallInfo (call graphs — associated Callee)
ir::rtl::Dialect (instruction-set vocabulary — Root, Operator, EffectOp,
│ WritePolicy, Outcome, NondetDomain)
└── ir::rtl::Fold (constant evaluation of operators)
ir::rtl::InstructionBuilder<D> (write-only emission into consumer storage)
ir::rtl::RecordView<D> (one sealed instruction record)
ir::rtl::FunctionView<D> (RootedView + blocks of instruction records)
ir::mlil::Dialect (Op, Type, Edge)
ir::mlil::FunctionView<D> (RootedView + blocks of flat SSA instructions)
ir::hlil::Dialect (Op, Constant, Type, Variable)
ir::hlil::FunctionView<D> (read-only statement and expression trees)
The ir traits follow the same rule. The consumer implements ir::rtl::FunctionView, ir::mlil::FunctionView, or ir::hlil::FunctionView on its own dense storage. The RTL and MLIL views are RootedViews, so the graph algorithms, such as DominatorTree::compute, run on them directly. The HLIL view is a pair of trees, not a graph.
MemoryEventInfo is the single instruction-side source of truth for memory.
Directional MemoryAccess::read, write, and read_modify_write
constructors keep loaded definitions, stored uses, and address uses distinct
(MemoryAccess::opaque covers table-driven kinds with unknown values);
events also retain exact locations, atomicity, and ordered fences. cfglib owns
this vocabulary only: memory SSA, alias oracles, and memory value flow over it
live in the il crate.
| Crate | Description |
|---|---|
| cfglib | Generic graph, CFG, and dataflow framework with the RTL, MLIL, and HLIL IR trait contracts |
Adapters live with the language they adapt, next to their decoders and test
corpora. The SM4/SM5 decompiler is in
d3dasm. Its cfg module also exposes
a raw CFG and component-granular SSA adapter. The Java/DEX decompiler is in
cafe.
A language picks its entry level. A machine-shaped language emits ir::rtl
records through InstructionBuilder into its own storage. A variable-shaped
language implements ir::mlil::FunctionView directly. A language with source
structure implements ir::hlil::FunctionView and FunctionBuilder. In
every case the storage, the builders, and the passes that edit them stay with
the language.
cfglib deliberately defines no generic LLIL. Low-level IRs are language-owned,
and the minimal traits that they share (FlowControl, InstrInfo, CfgView,
Cfg) are their common surface.
For symbol, reference, value-flow, type-relation, import, or grammar graphs, store domain objects directly in Graph<N, E>, or in KeyedGraph<K, N, E> when the domain identities are sparse keys. Projects that already own adjacency lists can instead implement GraphView for their store, then use traversal, shortest-path, topological-sort, SCC, dominance, loop-detection, metrics, and pattern algorithms without migrating their data. Dense u32 and usize handles work directly; custom handles implement DenseId.
For a control-flow adapter:
- Build
Cfg<I>directly withnew_block()/add_edge(), or useCfg<I, E>::with_edge_payload()plusadd_edge_with_payload()when branch labels, handler order, continuation/call-site identity, or source locations must survive. Structured unit-payload streams can instead implementFlowControland useCfgBuilder::build(). A consumer with its own block and edge storage implementsCfgViewinstead of building aCfg. - Optionally implement
InstrInfowith a nativeVariableidentity (and its sub-traits) for dataflow analyses. - Implement
DisplayInstrwhen you want DOT or text output.
Cfg<I, E> is a Graph<BasicBlock<I>, Edge<E>, BlockTag, EdgeTag> plus the entry block and the exception metadata that make it a control-flow graph; E defaults to () for the unannotated form. Two records describe what a change did, and they are not the same thing: Rewrite is a transform's sparse account — a missing entry means unchanged, an empty replacement list means removed, one replacement means retained or redirected, several ordered replacements mean a split — while Renumbering is the total account Cfg::compact gives when it renumbers the survivors. Rewrite::then_renumbered carries the first across the second. Stable parallel EdgeIds plus caller payloads are also the generic continuation/call-site mechanism; cfglib does not impose a VM-specific continuation type.
The variable type only needs Clone + Ord — never Copy, never numeric. It can be an architecture enum such as Register(Rax) / Flag(Zero), a shader structure such as (register file, index, component), an interned source symbol, or an existing IR value handle. Adapters decide the atomic aliasing unit; for overlapping resources such as x86 subregisters, expose canonical units or every affected unit.
use cfglib::{Cfg, DominatorTree, InstrInfo};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
enum X86Variable { Register(u8), Flag(u8), StackSlot(i32) }
struct X86Instruction {
uses: Vec<X86Variable>,
defs: Vec<X86Variable>,
}
impl InstrInfo for X86Instruction {
type Variable = X86Variable;
fn uses(&self) -> &[Self::Variable] { &self.uses }
fn defs(&self) -> &[Self::Variable] { &self.defs }
}
let cfg = Cfg::<X86Instruction>::new();
let dominators = DominatorTree::compute(&cfg);SSA construction over the same InstrInfo adapter lives in the il crate.
For an IR adapter, implement the ir::rtl or ir::mlil traits on your own dense storage. Keep every arena, builder, and editing pass in your crate. Run the graph algorithms of cfglib (dominators, post-dominators, loops, and the rest) on your function view, and apply their answers yourself; the facts over an RTL function, such as the root table and block summaries, are in il::rtl::facts. A machine-shaped language emits ir::rtl records through InstructionBuilder. A variable-shaped language implements ir::mlil::FunctionView directly. The consumer writes its own value analyses over that view, because they depend on the semantics of its dialect, and its own passes delete or rewrite instructions.
The concrete shader-bytecode adapter (the d3dasm decompiler's cfg
module) derives native register-component identities from decoded masks
and swizzles, retains relative index expressions, classifies multi-result
and UAV read-modify-write operations, and reports observable shader effects
through its own effect vocabulary.
The tests/source-cfg.rs integration test is the executable specification of
the source-language side: interned symbol variables, dominators, rendering,
and goto wiring by label token.
Every other target in crates/cfglib/tests/ pins one public area through the
crate facade only, the way a consumer calls it, and judges the library against
a reference written in the test (a brute-force definition, an interpreter run
before and after a transform, a round-trip law) rather than against the
library's own logic. A directory target (tests/<area>/main.rs) holds the
larger ones.
| Area | Targets |
|---|---|
| Graph store, tables, traversal | graph-store, fanout-tables, open-search, graph-algorithms, tombstones (every analysis on a store with removed slots, before and after compact) |
| Control flow and exceptions | control-tree, exception-regions, exception-models, address-cfg |
| Dataflow | dataflow-solvers |
| Transforms and analyses | transform-pipeline, cfg-analyses |
| IR contracts, each over its own toy dialect | rtl-contract, mlil-contract |
| Formats | text-dot, serde-round-trip (needs --features serde), no-std-facade |
mise install provides the toolchain, prek and nextest; mise run ci runs
every check; prek install installs the pre-push hooks (fmt, clippy, file
hygiene). The hooks live in prek.toml and run at push time only: every gate
in AGENTS.md must still pass before a change is complete. CI runs those gates
in two parallel jobs, plus a no_std build for x86_64-unknown-none.
AGENTS.md also holds the workspace's naming and layout conventions.
MIT