<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Tech Notes</title>
	<id>tag:neugierig.org,2010:tech-notes</id>
	<link href="https://neugierig.org/software/blog/"></link>
	<link rel="self" href="https://neugierig.org/software/blog/atom.xml"></link>
	<updated>2026-10-01T00:00:00Z</updated>
	<author>
		<name>Evan Martin</name>
		<email>evan.martin@gmail.com</email>
	</author>
	<entry>
		<id>tag:neugierig.org,2010:tech-notes/2026-10-01/guess-and-check</id>
		<updated>2026-10-01T00:00:00Z</updated>
		<title>The guess and check pattern</title>
		<link href="https://neugierig.org/software/blog/2026/10/guess-and-check.html"></link>
		<content type="html">&lt;p&gt;Here&#39;s a software pattern that I&#39;ve found new use for in our new world of&#xA;agents. I&#39;ll motivate it with an example from build systems.&lt;/p&gt;&#xA;&lt;p&gt;When using build systems you often write down some information that feels&#xA;redundant. For example, from the source code you can see that module A imports&#xA;from module B, but then the build system requires you to separately tell it to&#xA;build module B before building A. Why do we need to write this out, when it is&#xA;visible from the source? It is tempting to figure it out automatically, as tools&#xA;like &lt;a href=&#34;https://github.com/capnproto/ekam&#34;&gt;ekam&lt;/a&gt; do.&lt;/p&gt;&#xA;&lt;p&gt;The easy answer for why we don&#39;t always do this is the normal software answer of&#xA;path dependence and how software tends to be clunky. But there are also two good&#xA;reasons: information like this can be costly to gather and also can be hard to&#xA;get completely right. It can be costly if your language design you might need to&#xA;parse the whole source tree to find where a given module actually lives, as&#xA;&lt;a href=&#34;https://nibblestew.blogspot.com/2023/10/the-road-to-hell-is-paved-with-good.html&#34;&gt;perhaps with C++ modules&lt;/a&gt;.&#xA;It can be hard to get correct if there is configuration complexity; for example&#xA;in a C file with &lt;code&gt;#include &#34;config.h&#34;&lt;/code&gt;, it is not clear which of potentially&#xA;multiple &lt;code&gt;config.h&lt;/code&gt; candidates it means; a tool that guesses will be hard to&#xA;predict and sometimes wrong. (Just so I&#39;m not only picking on C, I&#39;ll add that&#xA;finding where a given &lt;code&gt;import ... from &#39;x&#39;&lt;/code&gt; in JavaScript resolves to has&#xA;&lt;a href=&#34;https://devdocs.io/html/reference/elements/script/type/importmap&#34;&gt;surprisingly complex semantics&lt;/a&gt;,&#xA;and can even depend on the path of the source file.)&lt;/p&gt;&#xA;&lt;p&gt;However, in the common case it really is often possible to automate things, and&#xA;it&#39;s not that costly. This leads us to what in my head I call the guess and&#xA;check pattern: &lt;em&gt;use a tool to automatically guess, edit and/or verify the&#xA;result, and write that down as the definitive answer&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For example &lt;a href=&#34;https://github.com/bazel-contrib/bazel-gazelle&#34;&gt;Gazelle&lt;/a&gt; updates&#xA;Bazel &lt;code&gt;BUILD&lt;/code&gt; files from Go source. Bazel then gets to run quickly without&#xA;parsing Go source, and if Gazelle gets things wrong, you can still edit the&#xA;files. As an additional benefit, you check the result in to source control,&#xA;which means a human gets to see what actually changed, and it pins down the&#xA;result.&lt;/p&gt;&#xA;&lt;p&gt;For another example of this pattern, consider &#34;lock files&#34; as used in&#xA;programming language package managers. When you &lt;code&gt;npm add&lt;/code&gt; or &lt;code&gt;cargo add&lt;/code&gt; etc.,&#xA;the tooling makes network requests, runs a&#xA;&lt;a href=&#34;https://doc.rust-lang.org/cargo/reference/resolver.html&#34;&gt;complex algorithm&lt;/a&gt; to&#xA;resolve what you meant, and writes down the result. Builds after this point can&#xA;use the lock files without looking around on the network or guessing about&#xA;versions.&lt;/p&gt;&#xA;&lt;p&gt;I used this pattern in my Windows emulator projects. For any given Windows&#xA;function&#xA;&lt;a href=&#34;https://neugierig.org/software/blog/2024/09/retrowin32-syscalls.html&#34;&gt;I declare the emulator&#39;s version of the function in Rust&lt;/a&gt;.&#xA;To automate writing out the function&#39;s matching argument list I can&#xA;&lt;a href=&#34;https://github.com/evmar/theseus/tree/main/win32/extract&#34;&gt;generate it from metadata published by Microsoft&lt;/a&gt;.&#xA;But I only need that metadata when I run the generator, and the output doesn&#39;t&#xA;need to be perfect! I can generate code that is most of the way there, because&#xA;it&#39;s fine as a human to fix up the details after. The generator can even just&#xA;make up type names that don&#39;t exist because the compiler will catch it&#xA;afterwards.&lt;/p&gt;&#xA;&lt;p&gt;Another way to look at this pattern is to see there&#39;s a chain of work to&#xA;guarantee determinism atop a nondeterministc start: you can use a tool that&#xA;makes a guess, and as long as you write down that guess and use deterministic&#xA;steps from that written state onward you still have determinism in the result.&lt;/p&gt;&#xA;&lt;p&gt;Which finally leads me to one way I&#39;ve been tentatively experimenting with AI.&#xA;AI behavior matches the shape I&#39;ve been describing. An agent can do a long slow&#xA;processing step and generate some output &amp;mdash; not limited to code, even things&#xA;like gathering data &amp;mdash; as long as I write the output down as a result and have a&#xA;human and/or tooling verification pass after. Keeping this pattern in mind has&#xA;helped me find places where AI is suitable: problems where I know up front that&#xA;a plausible guess is all I require, and that framing helps me keep in mind that&#xA;the output is always just a guess.&lt;/p&gt;&#xA;&lt;p&gt;(PS: just in case it wasn&#39;t obvious, my blog posts are always just me banging&#xA;away on a keyboard, no clankers will keep me from my meandering!)&lt;/p&gt;</content>
	</entry>
	<entry>
		<id>tag:neugierig.org,2010:tech-notes/2026-08-13/dominators</id>
		<updated>2026-08-13T00:00:00Z</updated>
		<title>Computing graph dominators</title>
		<link href="https://neugierig.org/software/blog/2026/08/dominators.html"></link>
		<content type="html">&lt;p&gt;A few years back I wrote about &lt;a href=&#34;https://neugierig.org/software/blog/2023/07/dominator.html&#34;&gt;the dominator tree of a dependency graph&lt;/a&gt;,&#xA;which is one of my favorite tricks for thinking about dependencies. It turns out&#xA;a new tinkering project of mine again needs a dominator tree, so I invested some&#xA;time to deepen my understanding.&lt;/p&gt;&#xA;&lt;p&gt;In this post I present an algorithm for computing graph dominators along with&#xA;the intuition behind it.&lt;/p&gt;&#xA;&lt;h2&gt;Definitions&lt;/h2&gt;&#xA;&lt;p&gt;There are two central definitions that I will handwave some details about; you&#xA;can read &lt;a href=&#34;https://en.wikipedia.org/wiki/Dominator_(graph_theory)&#34;&gt;Wikipedia&lt;/a&gt; for&#xA;those. Here&#39;s a graph to visualize them. Hover some nodes while you read.&lt;/p&gt;&#xA;&lt;div style=&#39;float:right&#39; id=&#39;viz-dom&#39;&gt;&lt;/div&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Node x &lt;em&gt;dominates&lt;/em&gt; node y if all paths from the graph&#39;s root (&lt;code&gt;a&lt;/code&gt;, in this&#xA;example) to y must go through x. If you hover a node here, its dominators are&#xA;shown in yellow.&lt;/li&gt;&#xA;&lt;li&gt;Node x &lt;em&gt;immediately dominates&lt;/em&gt; y if it is the lowest dominator above y. The&#xA;immediate dominator of the hovered node is shown with a thicker outline.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Again, see &lt;a href=&#34;https://neugierig.org/software/blog/2023/07/dominator.html&#34;&gt;my earlier post&lt;/a&gt; for some other framings of what these mean or&#xA;how to think about them.&lt;/p&gt;&#xA;&lt;p&gt;&lt;br clear=all&gt;&lt;/p&gt;&#xA;&lt;h2&gt;Choosing an algorithm&lt;/h2&gt;&#xA;&lt;p&gt;There is a continuous stream of research going back to 1959 publishing different&#xA;algorithms for computing dominators with varying levels of implementation&#xA;complexity. Lengauer-Tarjan (&#34;LT&#34;) from 1979 seems to be the standard but it is&#xA;relatively complex, involving spanning trees and union find.&lt;/p&gt;&#xA;&lt;p&gt;In LLVM, i.e. in a tool where performance really does matter, it appears they&#xA;use LT but have changed their implementation over time. For example&#xA;&lt;a href=&#34;https://groups.google.com/g/llvm-dev/c/_z7rQUwBNps&#34;&gt;in this work in 2017&lt;/a&gt; they&#xA;mention in a large compile they were computing 6.5 million dominator trees(!)&#xA;and they changed to an approach that supports incremental updates.&lt;/p&gt;&#xA;&lt;p&gt;The paper &#34;A Simple, Fast Dominance Algorithm&#34; from 2001 describes a simple&#xA;algorithm that they claim is both useful for learning and in practice about 2.5x&#xA;faster than LT.&lt;/p&gt;&#xA;&lt;p&gt;The later paper &#34;Finding Dominators in Practice&#34; compares multiple algorithms,&#xA;and regarding the above claim they write: &#34;a more careful implementation of&#xA;[Lengauer-Tarjan] later led to different results (personal communication)&#34;,&#xA;which is not a great sign. However, in that paper they also gather numbers&#xA;comparing five different algorithms across a collection of graphs and find that&#xA;they all land somewhere between 2-5x the time of a breadth-first search, which&#xA;itself they measure in microseconds. Which is to say, for the kinds of graphs&#xA;that you or I likely care about, the difference doesn&#39;t matter.&lt;/p&gt;&#xA;&lt;p&gt;If you like reading papers (I do! it&#39;s a worthy habit to develop!), you&#39;re best&#xA;off reading &#34;A Simple, Fast Dominance Algorithm&#34; directly. But in part for&#xA;deepening my own understanding by saying it in my own words, the rest of this&#xA;post will dive into the &#34;Simple, Fast&#34; algorithm.&lt;/p&gt;&#xA;&lt;h2&gt;The approach&lt;/h2&gt;&#xA;&lt;p&gt;Their presentation is roughly two parts. First, they describe a general approach&#xA;for computing dominators and why it works. Second, they show an algorithm that&#xA;uses some representation tricks to implement that approach efficiently.&lt;/p&gt;&#xA;&lt;p&gt;The general approach is describing the computation as a data-flow equation,&#xA;which defines a per-node computation that recursively depends on itself.&lt;/p&gt;&#xA;&lt;p&gt;Define &lt;code&gt;dom[n]&lt;/code&gt; as the set of dominators for node &lt;code&gt;n&lt;/code&gt;. Then the data flow&#xA;equation is:&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-moz-tab-size:2;-o-tab-size:2;tab-size:2;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;dom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;root&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#000&#34;&gt;root&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;dom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;intersect&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;dom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;p&lt;/span&gt;] &lt;span style=&#34;color:#a90d91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;predecessors&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;)) &lt;span style=&#34;color:#000&#34;&gt;union&lt;/span&gt; {&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In words, the dominator set of a node is the intersection of the dominators of&#xA;the node&#39;s predecessors, as well as the node itself. (To make sense of this,&#xA;don&#39;t overlook that &lt;code&gt;dom[n]&lt;/code&gt; always includes &lt;code&gt;n&lt;/code&gt;!)&lt;/p&gt;&#xA;&lt;p&gt;To compute this, you run in a loop that updates each node until the output stops&#xA;changing.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-moz-tab-size:2;-o-tab-size:2;tab-size:2;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;True&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;False&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a90d91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;nodes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;recompute&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;dom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;dom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;True&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In the paper, they connect this to other research that shows this will converge&#xA;on the correct answer in a relatively small number of iterations &amp;mdash; if you&#xA;iterate the nodes in reverse postorder, more on that in a moment. For our&#xA;purposes of intuition, I think it&#39;s enough to say &#34;this is guaranteed to&#xA;converge on the correct result fast enough, see the paper for proof&#34;.&lt;/p&gt;&#xA;&lt;p&gt;Why does this work? In the above sample graph, try hovering the predecessors of&#xA;node &lt;code&gt;g&lt;/code&gt; or &lt;code&gt;h&lt;/code&gt; and mentally intersect the sets in yellow to see it produce&#xA;their own yellow sets. Intuitively the sets represent something like a path from&#xA;the root (though they may not be a full path; witness the dominator set for&#xA;&lt;code&gt;g&lt;/code&gt;), and intersecting the sets results in the nodes found on all paths from the&#xA;root.&lt;/p&gt;&#xA;&lt;p&gt;As given this is inefficient to compute &amp;mdash; though I suspect if you&#39;re writing&#xA;Python or whatever and working with a small graph it&#39;s probably fine. The actual&#xA;algorithm from the paper is more efficient.&lt;/p&gt;&#xA;&lt;h2&gt;Traversal order&lt;/h2&gt;&#xA;&lt;p&gt;To get to the algorithm we first must detour into graph traversal, as it relies&#xA;on a reverse postorder traversal of the graph.&lt;/p&gt;&#xA;&lt;p&gt;A preorder traversal visits a node then its children; a postorder visits the&#xA;children before the node, recursively; a reverse postorder is the postorder&#39;s&#xA;order but reversed.&lt;/p&gt;&#xA;&lt;p&gt;Importantly, reverse postorder is different than preorder. In the below graph,&#xA;I&#39;ve numbered the nodes in their traversal order so you can compare them.&lt;/p&gt;&#xA;&lt;div id=&#39;viz-orders&#39;&gt;&lt;/div&gt;&#xA;&lt;p&gt;In a preorder traversal, the recursion makes its way all the way down the left&#xA;side to the bottom before visiting the right side, so the right child of 0 is&#xA;visited last. With a reverse postorder you get the invariant that each node is&#xA;visited before any of its children, which is the important property the&#xA;algorithm relies on.&lt;/p&gt;&#xA;&lt;p&gt;(I fear in writing this section that it is all rather obvious to you, reader. I&#xA;think many years of working on build systems has given me intuition for&#xA;algorithms over acyclic graphs and for whatever reason as soon as cycles get&#xA;involved I start getting confused. In the first graph in this post there is a&#xA;&#34;back edge&#34;, from &lt;code&gt;g&lt;/code&gt; to &lt;code&gt;b&lt;/code&gt;, but also the right way to think about it is that&#xA;in terms of a traversal &lt;code&gt;g&lt;/code&gt; still comes &#34;later&#34;.)&lt;/p&gt;&#xA;&lt;p&gt;By the way, in &#34;Finding Dominators in Practice&#34; when discussing this algorithm&#xA;and its use of postorder they write:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Initializing T as a [postorder] tree is bad both in theory and in practice&#xA;because it causes the back edges to be processed, even though they contribute&#xA;nothing to the [nearest common ancestors]. Intuitively, a much better&#xA;initial approximation of the dominator tree is a [breadth-first search]&#xA;tree.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This wording feels kind of aggressive to me! The general dataflow approach&#xA;produces the correct answer regardless of the iteration order, so changing the&#xA;order doesn&#39;t affect correctness, and in their results they measured both&#xA;approaches and found their idea improved performance by roughly 10% on the&#xA;graphs they were measuring.&lt;/p&gt;&#xA;&lt;p&gt;But I believe the original paper&#39;s proof of the bound on the number of&#xA;iterations relies on specifically reverse postorder. (I asked Claude about this&#xA;and it found a counterexample 14-node graph where the RPO order takes one pass&#xA;and the BFS order takes 3 passes.)&lt;/p&gt;&#xA;&lt;h2&gt;Dominator tree&lt;/h2&gt;&#xA;&lt;p&gt;With reverse postorder (&#34;RPO&#34;) defined, let&#39;s look at the actual algorithm.&lt;/p&gt;&#xA;&lt;p&gt;The first trick of the algorithm is that instead of computing dominator sets,&#xA;you instead compute for each node just its immediate dominator. If you look at&#xA;each node&#39;s immediate dominator as a parent pointer you get a dominator tree.&#xA;Given immediate dominators, you can read the dominator set of a given node by&#xA;walking the dominator tree upwards.&lt;/p&gt;&#xA;&lt;p&gt;Here&#39;s the first graph again, with its dominator tree (the thing we&#39;re trying to&#xA;compute) alongside it. Look at a node and its ancestors in the dominator tree,&#xA;and compare to the yellow nodes when you hover it on the left.&lt;/p&gt;&#xA;&lt;div id=&#39;viz-tree&#39;&gt;&lt;/div&gt;&#xA;&lt;p&gt;To compute immediate dominators, it&#39;s again an iterative data flow calculation.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-moz-tab-size:2;-o-tab-size:2;tab-size:2;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;root&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;root&lt;/span&gt;  &lt;span style=&#34;color:#177500&#34;&gt;# unlike the sets before, idom stores single nodes&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;intersect_dom&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;predecessors&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;n&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;For example, to compute the immediate dominator of node &lt;code&gt;g&lt;/code&gt;, we look at its&#xA;predecessors &lt;code&gt;d&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt; and walk the dominator tree upwards to find the place&#xA;where their dominator sets intersect.&lt;/p&gt;&#xA;&lt;p&gt;This is another cyclical definition, so we again iterate it for all nodes until&#xA;it stabilizes.&lt;/p&gt;&#xA;&lt;h2&gt;Meet point&lt;/h2&gt;&#xA;&lt;p&gt;The second trick of the algorithm is in how to efficiently find the&#xA;intersection. This is where the RPO matters. Here is the graph again with the&#xA;nodes labeled by their RPO index.&lt;/p&gt;&#xA;&lt;div id=&#39;viz-tree-rpo&#39;&gt;&lt;/div&gt;&#xA;&lt;p&gt;The RPO numbering gives the property that a parent always has a number lower&#xA;than its children. Given two &#34;fingers&#34; pointing at two nodes in the tree, to&#xA;find where they meet, move whichever finger is pointing at a larger number to&#xA;its parent. To intersect more than two predecessors, intersect two at a time.&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-moz-tab-size:2;-o-tab-size:2;tab-size:2;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;intersect&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt;, &lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt;]  &lt;span style=&#34;color:#177500&#34;&gt;# walk a upwards&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;b&lt;/span&gt;]  &lt;span style=&#34;color:#177500&#34;&gt;# walk b upwards&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a90d91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;a&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Try clicking a node with multiple predecessors in the graph (3, 6, or 7) to see&#xA;this in action. (Note that we&#39;re finding the meet point of the predecessors of&#xA;the node, which comes from the full graph, while the intersection operation uses&#xA;the idom tree, which is a subset of the graph. This means the moving circles&#xA;visually skip some nodes in the above graph.)&lt;/p&gt;&#xA;&lt;h2&gt;Details&lt;/h2&gt;&#xA;&lt;p&gt;In the above I&#39;ve been a bit loose about initialization: the code is reading&#xA;from a data structure while that data structure is still under construction,&#xA;which might feel like it wouldn&#39;t work. I think for intuition&#39;s purposes the&#xA;right way to think about it is that the &#34;&lt;code&gt;while changed&lt;/code&gt;&#34; loop effectively&#xA;recomputes everything after any change anywhere, and that it&#39;s also guaranteed&#xA;to converge.&lt;/p&gt;&#xA;&lt;p&gt;That looks inefficient, but the algorithm also does the per-node processing in&#xA;RPO, which is effectively &#34;top down&#34;. That doesn&#39;t guarantee a single pass, but&#xA;it does mean it&#39;s not that many passes. (For an example of how it isn&#39;t a simple&#xA;top down single pass, consider how the first time the algorithm visits node 3 in&#xA;the RPO it hasn&#39;t yet computed idom for its predecessor node 6.)&lt;/p&gt;&#xA;&lt;p&gt;Here&#39;s the complete Rust implementation I ended up with in my toy application. I&#xA;cannot guarantee it&#39;s correct as I am still learning, but it at least passes&#xA;some simple tests!&lt;/p&gt;&#xA;&lt;pre style=&#34;background-color:#fff;-moz-tab-size:2;-o-tab-size:2;tab-size:2;&#34;&gt;&lt;code&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// Inputs:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// nodes numbered in reverse postorder, so node 0 is the start&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// preds[i]: array of predecessors of node i&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// order[i]: the ith node visited in the reverse postorder&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// Output:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;// idom[i]: the immediate dominator of node i&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;Vec&lt;/span&gt;::&lt;span style=&#34;color:#000&#34;&gt;with_capacity&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;graph&lt;/span&gt;.&lt;span style=&#34;color:#000&#34;&gt;len&lt;/span&gt;());&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;unset&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;usize&lt;/span&gt;::&lt;span style=&#34;color:#000&#34;&gt;MAX&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;.&lt;span style=&#34;color:#000&#34;&gt;resize&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;graph&lt;/span&gt;.&lt;span style=&#34;color:#000&#34;&gt;len&lt;/span&gt;(), &lt;span style=&#34;color:#000&#34;&gt;unset&lt;/span&gt;);  &lt;span style=&#34;color:#177500&#34;&gt;// initialize all results to unset&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#1c01ce&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#1c01ce&#34;&gt;0&lt;/span&gt;;  &lt;span style=&#34;color:#177500&#34;&gt;// idom[start] is itself&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;true&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;false&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#177500&#34;&gt;// compute idom[i] for all nodes except the root&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a90d91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;order&lt;/span&gt;[&lt;span style=&#34;color:#1c01ce&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;..&lt;/span&gt;].&lt;span style=&#34;color:#000&#34;&gt;iter&lt;/span&gt;().&lt;span style=&#34;color:#000&#34;&gt;copied&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#177500&#34;&gt;// only consider predecessors that have been initialized&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;preds&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;preds&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt;].&lt;span style=&#34;color:#000&#34;&gt;iter&lt;/span&gt;().&lt;span style=&#34;color:#000&#34;&gt;copied&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .&lt;span style=&#34;color:#000&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#000&#34;&gt;|&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;j&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;unset&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;Some&lt;/span&gt;(&lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt;) &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;preds&lt;/span&gt;.&lt;span style=&#34;color:#000&#34;&gt;next&lt;/span&gt;() &lt;span style=&#34;color:#a90d91&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a90d91&#34;&gt;continue&lt;/span&gt;;  &lt;span style=&#34;color:#177500&#34;&gt;// node is not reachable&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#177500&#34;&gt;&lt;/span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;pred&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;preds&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a90d91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;mut&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;pred&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a90d91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;f2&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;f1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a90d91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;idom&lt;/span&gt;[&lt;span style=&#34;color:#000&#34;&gt;i&lt;/span&gt;] &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;new&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#000&#34;&gt;changed&lt;/span&gt; &lt;span style=&#34;color:#000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a90d91&#34;&gt;true&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;PS: If you&#39;re not familiar with Rust, you should know that this might look like&#xA;it&#39;s allocating where it isn&#39;t. The &lt;code&gt;.filter()&lt;/code&gt; call only creates a filtering&#xA;iterator, not an array, which inlines to a loop when it&#39;s read from. And the&#xA;&lt;code&gt;.iter().copied()&lt;/code&gt; calls mean to iterate by value rather than by reference,&#xA;where the copied thing here is just integers.&lt;/p&gt;&#xA;&lt;script src=&#39;graphs.js&#39; type=&#39;module&#39;&gt;&lt;/script&gt;</content>
	</entry>
	<entry>
		<id>tag:neugierig.org,2010:tech-notes/2026-07-22/jujutsu-tutorial</id>
		<updated>2026-07-22T00:00:00Z</updated>
		<title>Jujutsu tutorial</title>
		<link href="https://neugierig.org/software/blog/2026/07/jujutsu-tutorial.html"></link>
		<content type="html">&lt;p&gt;A friend asked me how to learn to use&#xA;&lt;a href=&#34;https://jj-vcs.dev&#34;&gt;the Jujutsu version control system&lt;/a&gt;, which has been my&#xA;favorite new piece of tech from the last few years.&lt;/p&gt;&#xA;&lt;p&gt;I thought I&#39;d quickly write up the tutorial I&#39;d wished I&#39;d had. That snowballed&#xA;into actually trying to write The Tutorial, complete with chapters and an index&#xA;and so on.&lt;/p&gt;&#xA;&lt;p&gt;(In particular it also contains a sales pitch about why learning this tool is&#xA;worth your time. This is uncomfortable for me because I am not really the sort&#xA;of person who advocates but I found it a useful exercise exactly because of&#xA;that. Also it received&#xA;&lt;a href=&#34;https://lobste.rs/s/beqyuc/evan_s_jujutsu_tutorial#c_vkoq0w&#34;&gt;this nice comment&lt;/a&gt;.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://evmar.github.io/jjtut/&#34;&gt;Go read the tutorial!&lt;/a&gt;&lt;/p&gt;</content>
	</entry>
</feed>