Skip to content

Graph

Description

Typesafe directed graph definition.

Node payloads are owned by the graph. Each live node occupies one internal slot and is referred to by a stable generation/index GraphNodeId. Edges are directed and tracked incrementally in both outgoing and incoming adjacency lists of those ids.

This container is designed for analysis-style workloads such as reachability, control-flow, dependency, and graph-rewrite passes where cheap node lookup, explicit traversal, and deferred destructive mutation matter more than object-oriented node wrappers.
Like the other generic containers in this project, each Graph(T) expansion creates a distinct anonymous type. Prefer a typedef when you need to reuse the same graph type across APIs.

Fields

Name Description
slots Internal slot storage for live and reusable nodes.
free_indices Reusable slot indices populated by deletion/clear.
pending_edge_removals Directed edges marked for removal on next commit.
copy_init Optional deep-copy callback for node payloads.
copy_deinit Optional deinit callback for node payloads.
live_count Number of currently live nodes.
edge_count Number of directed edges currently stored.
pending_delete_count Number of nodes marked for deletion but not yet committed.
mutation_epoch Structural mutation counter used by traversal helpers.
allocator Allocator bound to this graph. Its alignment field governs node payload alignment.

Usage example (from documentation)

  typedef Graph(int) IntGraph;
  IntGraph graph = GraphInit();

Usage example (Cross-references)

Usage examples (Cross-references)
    /// Generic directed graph implementation
    
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Log.h>
    #include <Misra/Std/Memory.h>
    
        if ((alignment > 1) && !graph_alignment_is_pow2(alignment)) {
            LOG_FATAL("Graph allocator alignment must be 1 or a power of two");
        }
    }
    static void graph_validate_slot_limit(const GenericGraph *graph) {
        if (VecLen(&graph->slots) > (u64)UINT32_MAX) {
            LOG_FATAL("Graph exceeded maximum supported slot count");
        }
    }
                1
            )) {
            LOG_FATAL("Graph failed to record a reusable slot index");
        }
    }
        in_idx       = graph_find_neighbor_index(in_neighbors, from);
        if (in_idx == SIZE_MAX) {
            LOG_FATAL("Graph reverse adjacency is inconsistent during edge removal");
        }
            } else {
                if (!graph_remove_edge_now(graph, from, neighbor_id)) {
                    LOG_FATAL("Graph failed to remove marked outgoing edge");
                }
                removed += 1;
            GraphNodeId to = VecLast(neighbors);
            if (!graph_remove_edge_now(graph, from, to)) {
                LOG_FATAL("Graph failed to remove outgoing edge during node deletion");
            }
            removed += 1;
    
        if (!graph) {
            LOG_FATAL("Expected a valid Graph pointer");
        }
    
        if (!MAGIC_MATCHES(graph->__magic, GRAPH_MAGIC)) {
            LOG_FATAL("Graph is uninitialized or corrupted");
        }
        // dereferencing the method table.
        if (!graph->allocator) {
            LOG_FATAL("Graph allocator pointer is NULL");
        }
        if (!graph->allocator->allocate || !graph->allocator->resize || !graph->allocator->remap ||
            !graph->allocator->deallocate) {
            LOG_FATAL("Graph allocator is not fully configured");
        }
    
        if (graph->live_count > VecLen(&graph->slots)) {
            LOG_FATAL("Graph live node count exceeds slot count");
        }
    
        if (graph->pending_delete_count > graph->live_count) {
            LOG_FATAL("Graph pending delete count exceeds live node count");
        }
    
        if ((graph->live_count + VecLen(&graph->free_indices)) != VecLen(&graph->slots)) {
            LOG_FATAL("Graph slot accounting is inconsistent");
        }
                    target_slot = graph_require_live_slot_const(graph, neighbor_id);
                    if (!graph_neighbors_contains(&target_slot->in_neighbors, self_id)) {
                        LOG_FATAL("Graph reverse adjacency is missing predecessor entry");
                    }
                }
                    source_slot = graph_require_live_slot_const(graph, predecessor_id);
                    if (!graph_neighbors_contains(&source_slot->out_neighbors, self_id)) {
                        LOG_FATAL("Graph reverse adjacency is missing outgoing edge");
                    }
                }
            u32 index = VecAt(&graph->free_indices, free_index_i);
            if ((u64)index >= VecLen(&graph->slots)) {
                LOG_FATAL("Graph free slot index out of bounds");
            }
    
            if (graph_slot_is_occupied(VecPtrAt((GraphSlots *)&graph->slots, index))) {
                LOG_FATAL("Graph free index points to an occupied slot");
            }
        }
    
            if (!graph_neighbors_contains(neighbors, pending->to)) {
                LOG_FATAL("Graph pending edge removal refers to a missing edge");
            }
        }
    
        if (graph->live_count != live_count) {
            LOG_FATAL("Graph live node count is inconsistent");
        }
    
        if ((graph->edge_count != out_edge_count) || (graph->edge_count != in_edge_count)) {
            LOG_FATAL("Graph edge count is inconsistent");
        }
    
        if (graph->pending_delete_count != marked_count) {
            LOG_FATAL("Graph pending delete count is inconsistent");
        }
        // Mark verified.
            if (graph_slot_is_occupied(slot) && graph_slot_is_marked(slot)) {
                if (VecLen(&slot->out_neighbors) || VecLen(&slot->in_neighbors)) {
                    LOG_FATAL("Graph marked node retained incident edges before release");
                }
                graph_release_slot(graph, slot, item_size);
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/Zstr.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Log.h>
    
    static bool test_graph_access_helpers(void) {
        WriteFmt("Testing Graph access helpers\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(Zstr) ZstrGraph;
        ZstrGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph  graph_a = GraphInit(&alloc);
        IntGraph  graph_b = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        };
    
        WriteFmt("[INFO] Starting Graph.Access tests\n\n");
        return run_test_suite(
            tests,
            deadend_tests,
            (int)(sizeof(deadend_tests) / sizeof(deadend_tests[0])),
            "Graph.Access"
        );
    }
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/Allocator/Heap.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Log.h>
    
    static bool test_graph_type_defaults(void) {
        WriteFmt("Testing Graph defaults\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
    
    static bool test_graph_aligned_init_and_id_layout(void) {
        WriteFmt("Testing Graph aligned init and node id layout\n");
    
        HeapAllocator alloc = HeapAllocatorInitAligned(32);
        HeapAllocator alloc = HeapAllocatorInitAligned(32);
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        };
    
        WriteFmt("[INFO] Starting Graph.Type tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Type");
    }
    
        WriteFmt("[INFO] Starting Graph.Type tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Type");
    }
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Log.h>
    
    static bool test_graph_node_visit_scratch_state(void) {
        WriteFmt("Testing Graph node scratch visit state\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
    
    static bool test_graph_query_and_unmark_node_deletion(void) {
        WriteFmt("Testing Graph node mark query and unmark\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
    
    static bool test_graph_query_and_unmark_edge_removal(void) {
        WriteFmt("Testing Graph edge mark query and unmark\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        };
    
        WriteFmt("[INFO] Starting Graph.Ops tests\n\n");
        return run_test_suite(
            tests,
            deadend_tests,
            (int)(sizeof(deadend_tests) / sizeof(deadend_tests[0])),
            "Graph.Ops"
        );
    }
    #include <Misra/Std/Zstr.h>
    #include <Misra/Std/Allocator/Heap.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Container/Str.h>
    #include <Misra/Std/Log.h>
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
    
    static bool test_graph_node_deep_copy(void) {
        WriteFmt("Testing Graph node deep-copy\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(Str) StrGraph;
        StrGraph    graph = GraphInitWithDeepCopy(str_init_copy, str_deinit, &alloc);
        Str         name  = StrInitFromZstr("alpha", &alloc);
    
    static bool test_graph_node_owned_str_rvalue(void) {
        WriteFmt("Testing Graph node owned Str r-value insertion\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(Str) StrGraph;
        StrGraph    graph = GraphInitWithDeepCopy(NULL, str_deinit, &alloc);
        GraphNodeId node_id;
    
    static bool test_graph_init_optional_allocator(void) {
        WriteFmt("Testing Graph init optional allocator\n");
    
        typedef Graph(Str) StrGraph;
        WriteFmt("Testing Graph init optional allocator\n");
    
        typedef Graph(Str) StrGraph;
    
        // intentional bypass: no public setter on `Allocator` for effort /
        };
    
        WriteFmt("[INFO] Starting Graph.Init tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Init");
    }
    
        WriteFmt("[INFO] Starting Graph.Init tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Init");
    }
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Log.h>
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph  = GraphInit(&alloc);
        int      owned  = 42;
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
    
    static bool test_graph_self_loop_and_predecessor_order(void) {
        WriteFmt("Testing Graph self-loop handling and predecessor order\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        };
    
        WriteFmt("[INFO] Starting Graph.Insert tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Insert");
    }
    
        WriteFmt("[INFO] Starting Graph.Insert tests\n\n");
        return run_test_suite(tests, (int)(sizeof(tests) / sizeof(tests[0])), NULL, 0, "Graph.Insert");
    }
    #include <Misra/Std/Allocator/Default.h>
    #include <Misra/Std/Zstr.h>
    #include <Misra/Std/Container/Graph.h>
    #include <Misra/Std/Container/Map.h>
    #include <Misra/Std/Container/Str.h>
    }
    
    typedef Graph(Str) CityGraph;
    typedef Map(Str, GraphNodeId) CityIndex;
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        typedef Map(GraphNodeId, u64) CountMap;
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        typedef Graph(int) IntGraph;
        IntGraph graph = GraphInit(&alloc);
        };
    
        WriteFmt("[INFO] Starting Graph.Foreach tests\n\n");
        return run_test_suite(
            tests,
            deadend_tests,
            (int)(sizeof(deadend_tests) / sizeof(deadend_tests[0])),
            "Graph.Foreach"
        );
    }
    #endif
    #if FEATURE_GRAPH
    #    include <Misra/Std/Container/Graph.h>
    #endif
    #if FEATURE_INT
    
    // clang-format off
    #include "Graph/Type.h"
    #include "Graph/Init.h"
    #include "Graph/Insert.h"
    // clang-format off
    #include "Graph/Type.h"
    #include "Graph/Init.h"
    #include "Graph/Insert.h"
    #include "Graph/Access.h"
    #include "Graph/Type.h"
    #include "Graph/Init.h"
    #include "Graph/Insert.h"
    #include "Graph/Access.h"
    #include "Graph/Foreach.h"
    #include "Graph/Init.h"
    #include "Graph/Insert.h"
    #include "Graph/Access.h"
    #include "Graph/Foreach.h"
    #include "Graph/Ops.h"
    #include "Graph/Insert.h"
    #include "Graph/Access.h"
    #include "Graph/Foreach.h"
    #include "Graph/Ops.h"
    #include "Graph/Memory.h"
    #include "Graph/Access.h"
    #include "Graph/Foreach.h"
    #include "Graph/Ops.h"
    #include "Graph/Memory.h"
    #include "Graph/Private.h"
    #include "Graph/Foreach.h"
    #include "Graph/Ops.h"
    #include "Graph/Memory.h"
    #include "Graph/Private.h"
    // clang-format on
    #include "Graph/Ops.h"
    #include "Graph/Memory.h"
    #include "Graph/Private.h"
    // clang-format on
Last updated on