diff --git a/AGENTS.md b/AGENTS.md index 6272028..bf1adea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,9 @@ All code follows the patterns established in `src/wapp/`. The project prefix is | Internal/static funcs | `_` + camelCase | `_resolveTopology`, `_execCmd` | | File-scope globals | `_` + camelCase | `_default_allocator` | | File-scope constants | SCREAMING_SNAKE | `MAX_NODE_NAME` | +| Variables | snake_case | `vertex_count`, `edge_node` | + +- **Variable names**: Prefer descriptive names (e.g. `vertex_count` over `vc`, `edge_node` over `en`). Short names (`i`, `j`, `n`) are acceptable only in tight loop counters or trivial index variables. Avoid single-letter names in non-trivial scopes, and avoid abbreviated names that require the reader to hold a mental dictionary. ### Formatting @@ -118,6 +121,55 @@ prefer SoA layouts, batch processing, and minimise pointer chasing. Keep the DAG in contiguous arrays (e.g. adjacency lists packed in flat buffers) rather than individually allocated linked structures. +### Graph / adjacency lists + +Adjacency list nodes must be **separately allocated from the vertex array**. +Never use the vertex struct itself as a linked-list node in another vertex's +adjacency chain — that shares the `next` pointer between two roles (vertex state +vs. edge linking) and causes edges to splice into unintended chains. + +```c +// correct — per-edge copy on the arena +static void addEdge(PrGraph *g, const WpAllocator *alloc, u64 from, u64 to) { + PrVertex *src = &g->vertices[from]; + PrVertex *dst = wpMemAllocatorAlloc(alloc, sizeof(PrVertex)); + if (!dst) { /* handle OOM */ return; } + dst->id = g->vertices[to].id; + dst->value = g->vertices[to].value; + dst->next = src->next; + src->next = dst; +} + +// wrong — reuses the destination vertex as the list node +static void addEdge_bad(PrGraph *g, u64 from, u64 to) { + PrVertex *src = &g->vertices[from]; + PrVertex *dest = &g->vertices[to]; + dest->next = src->next; + src->next = dest; // overwrites dest->next used elsewhere +} +``` + +Arena bump allocation (`wpMemAllocatorAlloc`) is the natural fit for building +graph structures: per-edge nodes live in the arena and are freed in one shot +when the arena is destroyed. + +Always NULL-check the result of `wpMemAllocatorAlloc` — even arena allocators +can fail if the backing buffer is exhausted. + +### WpArray usage + +| Scenario | API | Size must be… | +|----------|-----|---------------| +| Stack (local, short-lived) | `wpArrayWithCapacity` | compile-time constant | +| Heap (arena-backed) | `wpArrayAllocCapacity` | runtime value | + +`wpArrayWithCapacity` creates a VLA-like compound literal on the stack — +passing a runtime variable triggers undefined behaviour and compiler warnings. +Use `wpArrayAllocCapacity` with an arena allocator for runtime sizes. + +Initialise arrays with `WP_ARRAY_INIT_FILLED` to set `count = capacity` +immediately, allowing direct indexing. + ## Documentation Save research notes and implementation plans as markdown in `documents/`: