{"id":51003987,"url":"https://github.com/modulovalue/graphviz-cfg-plugin","last_synced_at":"2026-06-20T18:01:48.163Z","repository":{"id":362137168,"uuid":"1255981149","full_name":"modulovalue/graphviz-cfg-plugin","owner":"modulovalue","description":"Out-of-tree Graphviz layout plugin (-Kcfg) for control-flow graphs: grid layout with orthogonal bottom-to-top edge routing. Installs into stock Graphviz, no fork.","archived":false,"fork":false,"pushed_at":"2026-06-02T19:58:13.000Z","size":2321,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T21:29:02.940Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/modulovalue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T10:55:24.000Z","updated_at":"2026-06-02T19:58:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/modulovalue/graphviz-cfg-plugin","commit_stats":null,"previous_names":["modulovalue/graphviz-cfg-plugin"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/modulovalue/graphviz-cfg-plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modulovalue%2Fgraphviz-cfg-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modulovalue%2Fgraphviz-cfg-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modulovalue%2Fgraphviz-cfg-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modulovalue%2Fgraphviz-cfg-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modulovalue","download_url":"https://codeload.github.com/modulovalue/graphviz-cfg-plugin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modulovalue%2Fgraphviz-cfg-plugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34580039,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T18:01:47.317Z","updated_at":"2026-06-20T18:01:48.157Z","avatar_url":"https://github.com/modulovalue.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# graphviz `cfg` layout plugin\n\nA standalone, **out-of-tree** graphviz layout engine that lays out control-flow\ngraphs in the Compiler Explorer / Cutter style: blocks on a grid, edges leaving\nthe bottom of a block and entering the top of the next, routed as orthogonal\nright-angle lanes. Select it with `-Kcfg` (or `layout=cfg`).\n\nIt installs into a **stock, unmodified graphviz** — no fork, no rebuild of\ngraphviz. Everything else (every output format, every other engine) keeps\nworking normally.\n\n## Example\n\nThe optimized IL control-flow graph of a Dart function\n(`loop_and_branch.dart :: classify`), rendered with `dot -Kcfg`. The plugin does\nthe grid layout and orthogonal bottom-to-top edge routing; the per-token syntax\ncolors, optimizer \"heatmap\" row tints (e.g. the orange `CheckStackOverflow`\nsafepoint, the blue `CheckSmi` guard), dimmed plumbing (`ParallelMove`), and\ncolored branches all come from the input DOT's HTML-like labels. The generating\nsource is [`example/sample-cfg.dot`](example/sample-cfg.dot) — reproduce it with\n`dot -Kcfg -Tpng example/sample-cfg.dot -o out.png`.\n\n![cfg layout example](example/sample-cfg.png)\n\n## Build \u0026 install\n\nRequires a graphviz install discoverable via `pkg-config --exists libgvc`\n(e.g. `brew install graphviz`) and a C compiler.\n\n```sh\nmake            # build the shared library\nmake install    # copy into graphviz's plugin dir and run `dot -c`\nmake test       # render a sample CFG\n```\n\nThen:\n\n```sh\ndot -Kcfg -Tpng yourgraph.dot -o out.png\ndot -Kcfg -Tsvg yourgraph.dot -o out.svg\n```\n\n`make uninstall` removes it and re-registers the remaining plugins.\n\n## Usage\n\nStandard DOT. One engine-specific attribute:\n\n- `cfgbias=left|right` on an edge picks the vertical routing lane when the left\n  and right candidate columns are equidistant. Default is `right`; set `left`\n  for the classic \"true branch goes left\". Example:\n\n```dot\ndigraph CFG {\n  node [shape=box, fontname=\"Courier\"];\n  B0 [label=\"entry\"];\n  B1 [label=\"loop head\\nBranch i\u003cn\"];\n  B2 [label=\"body\"];\n  B3 [label=\"exit\"];\n  B0 -\u003e B1;\n  B1 -\u003e B2 [cfgbias=left];\n  B1 -\u003e B3 [cfgbias=right];\n  B2 -\u003e B1;            // back edge\n}\n```\n\n## Subgraphs / clusters\n\n`subgraph cluster_*` boxes are drawn (fill, border, and label), including nested\nclusters, using the standard cluster attributes (`label`, `style`, `color`,\n`pencolor`, `fillcolor`, `bgcolor`, `penwidth`, `labelloc`, …).\n\nThe whole graph is laid out by **one** `cfg_core` pass over all nodes and edges,\nso every edge shares the same routing lanes and edges never overlap or overdraw\n— including edges that cross cluster boundaries. Each cluster is then drawn as\nthe bounding box of its member nodes (recursively for nesting) plus an 8pt\nmargin and a reserved label band; `cfg_core` itself stays a pure,\ngrouping-agnostic function (all of this is glue in `cfg_layout.c`). Cluster\nlabels default to **top-left** (rather than graphviz's centered) so the edge\nentering a single-entry cluster doesn't run through the label; set\n`labeljust=c`/`r` to override.\n\nTwo limitations worth knowing:\n\n- **Non-contiguous clusters.** Because the box is the bounding box of wherever\n  the members landed, a cluster whose members are *not* a contiguous region of\n  the layout can have a box that also encloses non-members. Real CFG clusters\n  (loop bodies, regions) are contiguous, so this is rare; making it tight in\n  general needs rank/ordering constraints (dot-level) that this engine doesn't\n  implement.\n- **Edge attach points.** A node with several edges on one side is widened (its\n  minimum `width` is raised to fit the edge fan) so the edges attach *inside* the\n  box rather than on its corners, and a fan-out source is wide enough that all\n  its edges emanate from under it. Low-degree nodes are untouched. (Merge nodes\n  are still positioned under one predecessor rather than centered — that's\n  inherent to the grid algorithm, matching upstream Dart/CE.)\n\nPlain (non-cluster) subgraphs and rank constraints (`rank=same`) are ignored —\nthe engine derives rows from the CFG itself.\n\n## Limitations\n\n- **Ports honor horizontal position only.** `node:port` / compass / record /\n  HTML cell ports move where an edge attaches *along* a block's bottom (tail)\n  or top (head) edge — so e.g. a switch block fans its out-edges from under the\n  right cells. The CFG convention is kept: edges still leave the bottom and\n  enter the top, so the side/compass (n/s/e/w) is not otherwise honored. The\n  attach point is jogged after routing, so a ported edge can have an extra bend\n  near the block; the routing lanes themselves are not port-aware.\n- **Routing-lane spacing** is 18pt rather than upstream's 10pt — a deliberate\n  divergence so each edge's final vertical approach stays longer than a rendered\n  arrowhead (a 10pt lane lets graphviz's arrow clipping bend the last segment).\n\n## Files\n\n| File | Role |\n|---|---|\n| `cfg_core.{c,h}` | The layout algorithm. **No graphviz dependency** — a C port of Compiler Explorer's `graph-layout-core` (via the dart-il-explorer project). Sized nodes + edges in, node positions + orthogonal edge polylines out. |\n| `cfg_layout.c` | Glue to graphviz: sizes nodes, runs `cfg_core` once over the whole graph, writes `ND_coord`, routes/installs edge splines (honoring `node:port`), draws cluster (`subgraph cluster_*`) boxes around their members, sets the bounding box, hands off to graphviz's normal post-processing/rendering. |\n| `gvplugin_cfg.c` | Plugin registration (`gvplugin_cfg_LTX_library`). |\n| `test/` | Fixture-based regression suite (`make check`). See `test/README.md`. |\n\n## Testing\n\n`make check` (or `test/run.sh`) renders every fixture under `test/fixtures/`\nand diffs its `-Tdot` layout (node positions, edge splines, cluster boxes)\nagainst a committed golden, grouped into `layout`, `edges`, `subgraphs`,\n`ports`, and `realworld` categories. `test/compare-baseline.sh` additionally\nasserts the flat layout/edge output is byte-identical to the `git HEAD` plugin.\nRe-bless intentional changes with `test/gen-golden.sh`. Details in\n[`test/README.md`](test/README.md).\n\n## How it plugs in (and the one caveat)\n\ngraphviz layout engines are plugins: a shared library exporting\n`gvplugin_\u003cname\u003e_LTX_library`, discovered via `dot -c`. The public plugin API\n(`gvplugin.h`, `gvplugin_layout.h`, `types.h`, `geom.h`, `gvcjob.h`) is\ninstalled by graphviz, so registration uses only public headers.\n\nThe glue, however, calls a handful of graphviz helpers that are **exported from\n`libgvc` but not part of the documented public API** (`clip_and_install`,\n`compute_bb`, `gv_nodesize`, `common_init_node`, `dotneato_postprocess`,\n`setEdgeType`, `is_a_cluster`, `do_graph_label`, `free_label`, and the `State`\nglobal). Their declarations are not in installed\nheaders, so `cfg_layout.c` declares the prototypes itself. This is what lets the\nplugin work without a fork — but it couples the plugin to graphviz internals:\nbuild it against the graphviz version you run, and expect a possible recompile\nor small tweak across major graphviz releases. The algorithm core\n(`cfg_core.c`) touches zero graphviz symbols and is unaffected.\n\n## License\n\nBSD 2-Clause — see [`LICENSE`](LICENSE). The layout algorithm in `cfg_core.c`\nis a C port of Compiler Explorer's `graph-layout-core` (BSD 2-Clause), via the\ndart-il-explorer project; attribution is retained in the license file.\n\n## Status: a scrapped experiment\n\nThis was an experiment to see whether a control-flow-graph layout could be made\n**nicer than what graphviz already produces** for CFGs — the compact,\nblock-on-a-grid, orthogonal-bottom-to-top style of Cutter / Compiler Explorer,\ndelivered as a drop-in `-Kcfg` engine on stock graphviz.\n\nFor the core job — laying out and routing a flat CFG of basic blocks — it works\nwell. The trouble was everything else graphviz supports. Once you bring in the\n**advanced features** (clusters/subgraphs, record and HTML ports, edge labels,\nrank constraints, node sizing/shape machinery), a from-scratch layout engine has\nto re-implement a great deal that graphviz's own `dot` already does properly,\nand it turned out to be much harder than expected to make those features look as\ngood. Clusters needed a single global routing pass to avoid edge overlaps but\nthen can't guarantee tight boxes around non-contiguous members; ports are a\npost-routing approximation rather than true attachment; merge nodes aren't\ncentered; node boxes can't always be sized the way the shape code wants from a\nminimal plugin. Each of these is solvable, but solving them well amounts to\nre-doing what `dot` already implements — at which point the premise (a small,\nnicer alternative engine) no longer holds.\n\nSo **this experiment was scrapped.** It's preserved here as a working artifact —\nthe flat CFG layout is genuinely good, the test suite (`test/`) documents both\nwhat works and the limitations, and the renders under `test/render/` show the\nstate at which it was stopped — but it is not intended for further development.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodulovalue%2Fgraphviz-cfg-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodulovalue%2Fgraphviz-cfg-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodulovalue%2Fgraphviz-cfg-plugin/lists"}