You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: doc/cfgcpuReadme.md
+44Lines changed: 44 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -314,6 +314,50 @@ An `ExecutionContext` tracks graph state within a single flow of control:
314
314
-**Variant merging** minimizes SelectorNode creation. Most self-modifying code only touches operands (non-final fields), not opcodes.
315
315
-**Null wildcards in signatures** give a uniform matching mechanism that works for both merged instructions and SelectorNode dispatch.
316
316
-**Observer-based replacement** (`InstructionReplacerRegistry`) keeps caches and graph consistent when nodes are merged, without coupling components.
317
+
-**CfgBlock as a structural overlay** - blocks group straight-line instruction sequences without duplicating edge state. Successors/predecessors delegate by reference to the terminator/entry, so they stay in sync with the instruction-level graph automatically.
318
+
-**O(1) block liveness** via a maintained counter rather than iterating all contained instructions on every check.
319
+
-**Block boundary from instruction properties** - the linker determines boundaries exclusively from `IsBlockTerminator` and `IsBlockStarter` flags (set at parse time) plus static memory adjacency, never from `Kind` or `MaxSuccessorsCount` directly.
320
+
-**Monotonic discovery** - `IsDiscoveryComplete` flips from false to true exactly once and never back, simplifying reasoning about block state.
321
+
322
+
## CfgBlock
323
+
324
+
A `CfgBlock` groups a contiguous sequence of instructions that always execute together: one entry, one exit, no intermediate join points. It's a structural overlay on the instruction-level CFG - the instruction-level edges remain the single source of truth.
325
+
326
+
### Structure
327
+
328
+
-`Entry` - first instruction in the block.
329
+
-`Terminator` - last instruction (may be a `CfgInstruction` or a `SelectorNode`).
330
+
-`Instructions` - ordered list from entry through terminator inclusive.
331
+
-`IsDiscoveryComplete` - true once the linker has finalised the block.
332
+
-`IsLive` - true if every contained instruction is live (O(1) via maintained counter).
333
+
334
+
### Edge Delegation
335
+
336
+
Block-level `Successors` returns `Terminator.Successors` by reference. Block-level `Predecessors` returns `Entry.Predecessors` by reference. No separate block-edge state is stored - this eliminates sync bugs.
337
+
338
+
### Block Construction (NodeLinker)
339
+
340
+
The linker builds blocks incrementally as instruction-level edges are added:
341
+
342
+
1.**Bootstrap** - first edge from a node opens a one-node block for it.
343
+
2.**Continuation** - if the predecessor is not a terminator, the next node is not a starter, and they're memory-adjacent, the next node is appended to the predecessor's block.
344
+
3.**Boundary** - otherwise, the predecessor's block is closed and the next node gets its own block (new or split from an existing one).
345
+
4.**Split** - when a new edge targets the interior of an existing block, the block is split at that point.
346
+
347
+
### Self-Modifying Code and Blocks
348
+
349
+
When `ReplaceInstruction` fires (variant merging), the replacement happens in-place within the block (`ReplaceInPlace`). The new instruction inherits the block position and back-pointer. Subsequent edge rewires hit the intra-block idempotency check and don't cause spurious splits.
350
+
351
+
When a `SelectorNode` is injected via `CreateSelectorNodeBetween`, it either absorbed into the predecessor's block as its terminator (if the continuation rule applies) or lands in its own one-node block (if the predecessor is already a terminator).
352
+
353
+
### Hot-Path Execution
354
+
355
+
`CfgCpu.ExecuteNext` dispatches two ways depending on the resolved next node:
356
+
357
+
-**Hot path** – if the next node is the entry of a discovered, live block (`next == block.Entry`), `ExecuteBlock` walks the block's instruction list directly without re-entering the feeder between steps. This skips feeder lookup, linker calls, and memory reconciliation for every non-terminator instruction in the block.
358
+
-**Cold path** – in every other case (incomplete block, non-live block, or next node is an interior node of a complete live block), `ExecuteOneNode` steps exactly that one node.
359
+
360
+
The interior-node cold-step case is expected and valid. It arises during incremental discovery, selector insertion, instruction replacement, fault edges, and other graph surgery – none of which should be treated as an error. External interrupts fire once per dispatch, after the last node actually executed (block or single node).
0 commit comments