<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://www.cs.cornell.edu/~asampson//</id>
  <title>Adrian Sampson</title>
  <updated>2026-03-06T22:12:36+00:00</updated>
  <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/"/>
  <link rel="self" href="https://www.cs.cornell.edu/~asampson/blog.xml"/>
  <icon>https://www.cs.cornell.edu/~asampson/media/icon/favicon.ico</icon>
  <author>
    <name>Adrian Sampson</name>
    <uri>https://www.cs.cornell.edu/~asampson/</uri>
  </author>

  
  <entry>
    
    <id>https://www.cs.cornell.edu/~asampson/blog/gator.html</id>
    
    <title type="html">Geometry Bugs and Geometry Types</title>
    <updated>2024-08-08T00:00:00+00:00</updated>
    <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/blog/gator.html"/>
    <content type="html">&lt;figure style=&quot;width: 350px&quot;&gt;
  &lt;canvas width=&quot;350&quot; height=&quot;350&quot; id=&quot;diffuse-correct&quot;&gt;&lt;/canvas&gt;
  &lt;figcaption&gt;The diffuse component of the classic &lt;a href=&quot;https://en.wikipedia.org/wiki/Phong_reflection_model&quot;&gt;Phong lighting model&lt;/a&gt;, implemented (probably) correctly.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Phong_reflection_model&quot;&gt;Phong lighting model&lt;/a&gt; is the &amp;ldquo;hello world&amp;rdquo; of 3D rasterization effects.
Implementing it seems like an appropriate level of challenge for me, a total graphics neophyte.
Even a textbook rendering effect, however, entails some geometric thinking that in turn creates the possibility for its own special category of bug.
Let&amp;rsquo;s follow our nose and run into some geometry bugs.&lt;/p&gt;

&lt;p&gt;Phong lighting has three parts:
some uniform &lt;em&gt;ambient&lt;/em&gt; lighting,
a &lt;em&gt;diffuse&lt;/em&gt; component that looks the same from any angle,
and a &lt;em&gt;specular&lt;/em&gt; component that adds shiny highlights for direct reflections.
To make things even easier, let&amp;rsquo;s try to implement just the diffuse component.
It&amp;rsquo;s supposed to look like this bunny here.&lt;/p&gt;

&lt;p&gt;The idea is to compute the &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambertian_reflectance&quot;&gt;Lambertian reflectance&lt;/a&gt;.
At every point on the surface of the object, it&amp;rsquo;s the dot product between the surface&amp;rsquo;s normal vector and the direction from that point to the light source:&lt;/p&gt;

&lt;div class=&quot;kdmath&quot;&gt;$$
\mathit{lambertian} = \mathsf{max}(\mathit{lightDir}\cdot\mathit{fragNorm}, 0)
$$&lt;/div&gt;

&lt;p&gt;That $\mathsf{max}$ helps us ignore light coming from behind the rabbit&amp;rsquo;s surface.
We&amp;rsquo;re implementing this in a &lt;a href=&quot;https://www.khronos.org/opengl/wiki/Fragment_Shader&quot;&gt;fragment shader&lt;/a&gt;, so that
$\mathit{fragNorm}$ is the normal vector for a given point on the triangle that we&amp;rsquo;re rendering.
We can get $\mathit{lightDir}$ by normalizing the difference between the current fragment position and some absolute position of the light source:&lt;/p&gt;

&lt;div class=&quot;kdmath&quot;&gt;$$
\mathit{lightDir} = \mathsf{normalize}(\mathit{lightPos} - \mathit{fragPos})
$$&lt;/div&gt;

&lt;p&gt;Here&amp;rsquo;s a direct translation of all that into &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders&quot;&gt;GLSL&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uLightPos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;gl_FragColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uDiffColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We&amp;rsquo;ve set this fragment shader up so it gets its inputs, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uLightPos&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt;, from the CPU.
To finish it off, we multiply the Lambertian reflectance magnitude by an RGB color we&amp;rsquo;ve chosen for our light and, finally, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec4(..., 1.0)&lt;/code&gt; to set the &lt;a href=&quot;https://en.wikipedia.org/wiki/Alpha_compositing&quot;&gt;alpha channel&lt;/a&gt; to 1.&lt;/p&gt;

&lt;figure style=&quot;width: 350px&quot;&gt;
  &lt;canvas width=&quot;350&quot; height=&quot;350&quot; id=&quot;diffuse-naive&quot;&gt;&lt;/canvas&gt;
  &lt;figcaption&gt;What happens if you try to implement that diffuse lighting by translating the math 1-1 into GLSL.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This shader is incorrect.
The bunny looks like this: the shading seems mostly right, but the invisible light seems to be rotating along with the model instead of staying put.&lt;/p&gt;

&lt;p&gt;The math is right, but the code has a &lt;em&gt;geometry bug&lt;/em&gt;.
The problem is that, when you translate geometric math into rendering code, you have to represent the abstract vectors as concrete tuples of floating-point numbers.
When the math&amp;rsquo;s $\mathit{fragPos}$ becomes the shader&amp;rsquo;s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt;, we need to decide on its &lt;em&gt;reference frame:&lt;/em&gt;
will it be local coordinates relative to the bunny itself,
relative to the camera,
or using some absolute coordinates for the whole scene?
We also need to pick a coordinate system, like
ordinary Cartesian coordinates, polar coordinates, or the more graphics-specific system of &lt;a href=&quot;https://en.wikipedia.org/wiki/Homogeneous_coordinates&quot;&gt;homogeneous coordinates&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The real problem here is that none of these choices show up in the type system.
In GLSL, all our vectors are &lt;a href=&quot;https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec3&lt;/code&gt;s&lt;/a&gt;.
There are several different reference frames at work here, but none of them show up in the programming language.
GLSL is perfectly happy to add and subtract vectors that use completely different representations, yielding meaningless results.&lt;/p&gt;

&lt;p&gt;This post will walk through fixing the implementation of this math.
It&amp;rsquo;s written for people who don&amp;rsquo;t know much about graphics or geometry, i.e., people like me.
The goal here is to convince you that this kind of programming needs a type system.&lt;/p&gt;

&lt;h2 id=&quot;managing-reference-frames&quot;&gt;Managing Reference Frames&lt;/h2&gt;

&lt;figure style=&quot;max-width: 300px&quot;&gt;
  &lt;img src=&quot;/~asampson/media/gator/spaces.svg&quot; class=&quot;bonw&quot; alt=&quot;A 2D artist&apos;s interpretation of the model, world, and view reference frames in graphics rendering.&quot; /&gt;
  &lt;figcaption&gt;Rendering a whole scene usually involves several &lt;em&gt;model&lt;/em&gt; reference frames, a fixed &lt;em&gt;world&lt;/em&gt; frame, and a &lt;em&gt;view&lt;/em&gt; frame for the camera&apos;s perspective.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The first thing that&amp;rsquo;s wrong is that our vectors are in different reference frames.
It&amp;rsquo;s useful to imagine a single, fixed &lt;em&gt;world&lt;/em&gt; frame that contains the entire scene:
here, both our bunny and our light source.
The geometric data describing individual objects arrives each object&amp;rsquo;s own individual &lt;em&gt;model&lt;/em&gt; reference frame.
When you download the &lt;a href=&quot;https://en.wikipedia.org/wiki/Wavefront_.obj_file&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.obj&lt;/code&gt; file&lt;/a&gt; for the &lt;a href=&quot;https://faculty.cc.gatech.edu/~turk/bunny/bunny.html&quot;&gt;Stanford bunny&lt;/a&gt;, it doesn&amp;rsquo;t know about your renderer&amp;rsquo;s world frame:
the vertex positions and surface normals have to come represented in an intrinsic bunny-specific space.&lt;/p&gt;

&lt;p&gt;In our little shader, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt; are vectors in the bunny&amp;rsquo;s model frame while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uLightPos&lt;/code&gt; is a point in world space.
So the subtraction &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uLightPos - vPosition&lt;/code&gt; doesn&amp;rsquo;t yield a geometrically meaningful vector,
and we should probably be careful about that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot(lightDir, vNormal)&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;Renderers use transformation matrices to convert between reference frames.
Let&amp;rsquo;s suppose that we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt; matrix that transforms bunny space to world space.
Then the GLSL we need should look something like this:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uLightPos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With all our vectors converted to the common (world) reference frame, these operations should mean something!&lt;/p&gt;

&lt;p&gt;However, in realistic renderers, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt; transformation will actually be a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mat4&lt;/code&gt;, not a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mat3&lt;/code&gt;.
The reason has to do with affine transformations and coordinate systems&amp;mdash;we&amp;rsquo;ll address that next.&lt;/p&gt;

&lt;h2 id=&quot;converting-coordinate-systems&quot;&gt;Converting Coordinate Systems&lt;/h2&gt;

&lt;p&gt;In 3-dimensional space, a square 3&amp;times;3 matrix is a nice way to represent &lt;em&gt;linear&lt;/em&gt; transformations:
rotations, scaling, shearing, all that.
But renderers typically also want to do translation, which requires generalizing to &lt;em&gt;affine&lt;/em&gt; transformations.
You can&amp;rsquo;t represent those in a 3&amp;times;3 matrix, so what can we do?&lt;/p&gt;

&lt;p&gt;The usual way is to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Homogeneous_coordinates&quot;&gt;homogeneous coordinates&lt;/a&gt;.
Where ordinary Cartesian coordinates represent a point in 3-space using a GLSL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec3&lt;/code&gt;, homogeneous coordinates use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec4&lt;/code&gt;.
The extra coordinate is a scaling factor, so the 4-tuple $[x, y, z, w]$ represents the point at $[x/w, y/w, z/w]$.
Transformation matrices are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mat4&lt;/code&gt;s that can represent the full range of affine transformations in 3-dimensional space.&lt;/p&gt;

&lt;p&gt;In our example (and in any typical renderer setup),
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt; come to us in Cartesian coordinates (i.e., GLSL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec3&lt;/code&gt;s)
and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt; transformation matrix comes in homogeneous coordinates (i.e., a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mat4&lt;/code&gt;).
To fix our transformation expression &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel * vPosition&lt;/code&gt;, we&amp;rsquo;ll need something like this:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldCart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure style=&quot;width: 350px&quot;&gt;
  &lt;canvas width=&quot;350&quot; height=&quot;350&quot; id=&quot;diffuse-alltrans&quot;&gt;&lt;/canvas&gt;
  &lt;figcaption&gt;The same shader after applying the same coordinate-system juggling to both input vectors. (Particularly mysterious in dark mode.)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We convert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt; to homogeneous coordinates by tacking on a scaling factor $w=1$,
transform with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt;,
and then convert back to Cartesian coordinates by dividing by $w$ again.&lt;/p&gt;

&lt;p&gt;With this, we finally have our vertex position in world reference frame.
Let&amp;rsquo;s try repeating exactly the same process with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt;, the other model-space vector involved in our computation.
Sadly, something&amp;rsquo;s still very wrong&amp;mdash;in fact, the bunny somehow looks every worse than it did before.
&lt;span class=&quot;dark-only&quot;&gt;
If you&amp;rsquo;re viewing this page in dark mode, it may not be visible at all.
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The problem now is that, while the code we wrote to juggle homogeneous coordinates for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt; is correct,
the same treatment doesn&amp;rsquo;t work for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt;.
The reason has to do with the different kinds of geometric objects that these variables represent.&lt;/p&gt;

&lt;h2 id=&quot;kinds-of-geometric-objects&quot;&gt;Kinds of Geometric Objects&lt;/h2&gt;

&lt;p&gt;Yet again, we&amp;rsquo;ve been bitten by a geometric concept that remains invisible in the GLSL code.
There&amp;rsquo;s an important difference between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vPosition&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNormal&lt;/code&gt;: both are 3-dimensional Cartesian vectors,
but one represents a &lt;em&gt;position&lt;/em&gt; while the other is just a &lt;em&gt;direction&lt;/em&gt;.
These might seem like the same thing: what is a direction other than a unit-magnitude position?&lt;/p&gt;

&lt;figure style=&quot;max-width: 350px&quot;&gt;
  &lt;img src=&quot;/~asampson/media/gator/translation.svg&quot; class=&quot;bonw&quot; alt=&quot;Two reference frames, one a translation of the other. There is a point and a direction in each. The point gets translated; the direction stays the same.&quot; /&gt;
  &lt;figcaption&gt;We want reference-frame translations to affect positions but not directions.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The distinction matters when we convert between reference frames.
Remember that our homogeneous-coordinates transformation matrix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt; can encode a translation (in addition to rotation and scaling and all that).
We absolutely want to translate positions when going from the model frame to the world frame,
but &lt;em&gt;we do not want to translate directions&lt;/em&gt;.
When you shift a reference frame over some distance, all the points should move, but directions stay pointing in the same direction&amp;mdash;they should not get shifted over by the same amount.&lt;/p&gt;

&lt;p&gt;Homogeneous coordinates have a trick to deal with positions.
If you set their scaling factor, $w$, to zero, then transformation matrices will treat them correctly: they&amp;rsquo;ll apply all the linear transformations and none of the (affine) translation.
Then, to convert back to Cartesian coordinates, we have to ignore $w$ to avoid dividing by zero.
Here&amp;rsquo;s how it looks in GLSL:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key thing to notice is that Cartesian/homogeneous conversion needs to work differently for positions and directions.
So programmers have to keep track of the different kinds of geometric objects they&amp;rsquo;re dealing with in their heads.&lt;/p&gt;

&lt;h2 id=&quot;the-correct-shader&quot;&gt;The Correct Shader&lt;/h2&gt;

&lt;p&gt;Here&amp;rsquo;s a complete version of the correct shader:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// lightDir = normalize(lightPos - fragPos)&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldCart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldHom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uLightPos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorldCart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// lambertian = max(dot(lightDir, fragNorm), 0)&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normWorld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;gl_FragColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uDiffColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This shader produces the bunny at the top of this post.&lt;/p&gt;

&lt;h2 id=&quot;geometry-types&quot;&gt;Geometry Types&lt;/h2&gt;

&lt;p&gt;At &lt;a href=&quot;https://2020.splashcon.org/track/splash-2020-oopsla&quot;&gt;OOPSLA 2020&lt;/a&gt;, &lt;a href=&quot;https://www.cs.cornell.edu/~dgeisler/&quot;&gt;Prof. Dietrich Geisler&lt;/a&gt; published &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3428241&quot;&gt;a paper&lt;/a&gt; about geometry bugs
and a type system that can catch them.
The idea hasn&amp;rsquo;t exactly taken over the world, and I wish it would.
The paper&amp;rsquo;s core insight is that, to do a good job with this kind of type system, you need your types to encode three pieces of information:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the reference frame (like model, world, or view space)&lt;/li&gt;
  &lt;li&gt;the coordinate scheme (like Cartesian, homogeneous, or polar coordinates)&lt;/li&gt;
  &lt;li&gt;the geometric object (like positions and directions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Dietrich&amp;rsquo;s language, these types are spelled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheme&amp;lt;frame&amp;gt;.object&lt;/code&gt;.
Dietrich implemented these types in a language called &lt;a href=&quot;https://github.com/cucapra/gator&quot;&gt;Gator&lt;/a&gt; with help from &lt;a href=&quot;https://www.cis.upenn.edu/~euisuny/&quot;&gt;Irene Yoon&lt;/a&gt;, &lt;a href=&quot;https://aditink.github.io&quot;&gt;Aditi Kabra&lt;/a&gt;, &lt;a href=&quot;https://horace.io&quot;&gt;Horace He&lt;/a&gt;, and Yinnon Sanders.
With a few helper functions, you can get Gator to help you catch all the geometric pitfalls we saw in this post.
Here&amp;rsquo;s a version of our shader in Gator:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cart3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hom_reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;homify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cart3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uLightPos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posWorld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;cart3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;direction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hom_reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;homify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scalar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalWorld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The standard library comes with overloaded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;homify&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hom_reduce&lt;/code&gt; functions that do the right thing when converting a given geometric object between coordinate systems.
Gator also distinguishes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direction&lt;/code&gt;, which is a subtype that is guaranteed to have unit magnitude.
If you forget a transformation or conversion, Gator will report a type error.&lt;/p&gt;

&lt;p&gt;With geometry baked into the type system, we can also go one step farther and automatically generate the transformation code.
Gator supports an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in&lt;/code&gt; expression that searches for a transformation from one reference frame or coordinate system to another.
For example, if we mark &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uModel&lt;/code&gt; as the &lt;em&gt;canonical&lt;/em&gt; transformation from model space to world space, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in world&lt;/code&gt; suffices to manage both the reference-frame change and the detour through homogeneous coordinates:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uLightPos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vPosition&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scalar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambertian&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vNormal&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We at last have a version of the shader that looks kind of like the math.&lt;/p&gt;

&lt;p&gt;I know the world of &lt;a href=&quot;https://en.wikipedia.org/wiki/Shading_language&quot;&gt;shading languages&lt;/a&gt; is not exactly a hotbed of rapid innovation these days.
Even so, I think geometry types are a pretty good idea and I hope that some future generation of rendering systems borrows an idea or two from Gator.&lt;/p&gt;

</content>
    <summary type="html">&lt;p&gt;A special kind of bug exists in code that has to deal with geometric concepts like positions, directions, coordinate systems, and all that.
Long ago, in &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3428241&quot;&gt;an OOPSLA 2020 paper&lt;/a&gt;, we defined &lt;em&gt;geometry bugs&lt;/em&gt; and designed a &lt;a href=&quot;https://github.com/cucapra/gator&quot;&gt;type system&lt;/a&gt; to catch them.
This post demonstrates the idea through some buggy &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders&quot;&gt;GLSL&lt;/a&gt; shaders.&lt;/p&gt;

</summary>
  </entry>
  
  <entry>
    
    <id>https://www.cs.cornell.edu/~asampson/blog/bril.html</id>
    
    <title type="html">Bril: An Intermediate Language for Teaching Compilers</title>
    <updated>2024-07-26T00:00:00+00:00</updated>
    <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/blog/bril.html"/>
    <content type="html">&lt;p&gt;When I started a new &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2023fa/&quot;&gt;PhD-level compilers course&lt;/a&gt; a few years ago,
I thought it was important to use a &amp;ldquo;hands-on&amp;rdquo; structure.
There is a big difference between understanding an algorithm on a whiteboard and implementing it, inevitably running into bugs when your implementation encounters real programs.
At the same time, I wanted students to get started quickly, without learning the overwhelming APIs that come with industrial-strength compilers.&lt;/p&gt;

&lt;p&gt;I created &lt;a href=&quot;https://capra.cs.cornell.edu/bril/&quot;&gt;Bril&lt;/a&gt;, the Big Red Intermediate Language, to support the class&amp;rsquo;s implementation projects.
Bril isn&amp;rsquo;t very interesting from a compiler engineering perspective, but
I think it&amp;rsquo;s pretty good for the specific use case of teaching compilers classes.
Here&amp;rsquo;s a factorial program:&lt;/p&gt;

&lt;div class=&quot;language-bril highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;@main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;@fact&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;@fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;le&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;br&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cond&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;.then&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;.else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;.then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;.else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;decr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;@fact&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;decr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;prod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mul&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;prod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Bril is the only compiler IL I know of that is specifically designed for education.
Focusing on teaching means that Bril prioritizes these goals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is fast to get started working with the IL.&lt;/li&gt;
  &lt;li&gt;It is easy to mix and match components that work with the IL, including things that fellow students write.&lt;/li&gt;
  &lt;li&gt;The semantics are simple, without too many distractions.&lt;/li&gt;
  &lt;li&gt;The syntax is ruthlessly regular.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bril is different from other ILs because it prioritizes those goals above other, more typical ones:
code size, compiler speed, and performance of the generated code.&lt;/p&gt;

&lt;p&gt;Aside from that inversion of priorities, Bril looks a lot like any other modern compiler IL.
It&amp;rsquo;s an instruction-based, assembly-like, typed, &lt;a href=&quot;https://en.wikipedia.org/wiki/A-normal_form&quot;&gt;ANF&lt;/a&gt; language.
There&amp;rsquo;s a quote from &lt;a href=&quot;https://en.wikipedia.org/wiki/Why_the_lucky_stiff&quot;&gt;why the lucky stiff&lt;/a&gt; where he introduces &lt;a href=&quot;https://camping.github.io/camping.io/&quot;&gt;Camping&lt;/a&gt;, the original web microframework, as &amp;ldquo;a little white blood cell in the vein of Rails.&amp;rdquo;
If LLVM is an entire circulatory system, Bril is a single blood cell.&lt;/p&gt;

&lt;h2 id=&quot;bril-is-json&quot;&gt;Bril is JSON&lt;/h2&gt;

&lt;p&gt;Bril programs are JSON documents.
Here&amp;rsquo;s how students get started working with Bril code using Python:&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I&amp;rsquo;m obviously being a little silly here.
But seriously, the JSON-as-syntax idea is in service of the &lt;em&gt;fast to get started&lt;/em&gt; and &lt;em&gt;easy to mix and match components&lt;/em&gt; goals above.
I wanted Bril to do these things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Let students use any programming language they want.&lt;/strong&gt;
I wanted my compilers course to be accessible to lots of PhD students, including people with only tangential interest in compilers.
Letting them use the languages they&amp;rsquo;re comfortable with is a great way to avoid any ramp-up phase where students must learn some specific &amp;ldquo;realistic&amp;rdquo; compiler implementation language if they don&amp;rsquo;t already know it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No framework is required to get started.&lt;/strong&gt;
For the first offering of CS 6120, no libraries existed, and I needed to run the course somehow.
Beyond that practical matter, this constraint is valuable as a complexity limiter:
students can get started with simple stuff without learning any APIs.
These days, Bril does come with libraries that are great for avoiding JSON-handling frustrations when you scale up:
for &lt;a href=&quot;https://github.com/sampsyo/bril/tree/main/bril-rs&quot;&gt;Rust&lt;/a&gt;, &lt;a href=&quot;https://github.com/sampsyo/bril/tree/main/bril-ocaml&quot;&gt;OCaml&lt;/a&gt;, &lt;a href=&quot;https://github.com/sampsyo/bril/tree/main/bril-swift&quot;&gt;Swift&lt;/a&gt;, and &lt;a href=&quot;https://github.com/sampsyo/bril/tree/main/bril-ts&quot;&gt;TypeScript&lt;/a&gt;.
But the fact that they&amp;rsquo;re not really &lt;em&gt;required&lt;/em&gt; keeps the onramps gentle.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Compose small pieces with Unix pipelines.&lt;/strong&gt;
You can wire up Bril workflows with shell pipelines, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat code.json | my_opt | my_friends_opt | brilck&lt;/code&gt;.
I want students in CS 6120 to freely share code with each other and to borrow bits of functionality I wrote.
For a PhD-level class, this trust-based &amp;ldquo;open-source&amp;rdquo; course setup makes way more sense to me than a typical undergrad-style class where collaboration is forbidden.
Piping JSON from one tool to the next is a great vehicle for sharing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, JSON is the canonical form for Bril code.
Here&amp;rsquo;s a complete Bril program:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;functions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;instrs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;const&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;int&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;const&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;int&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;add&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;int&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;print&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This program has one function, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;, with no arguments and 4 instructions:
two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const&lt;/code&gt; instructions, an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even though Bril is JSON, it also has a text form.
I will, however, die on the following hill:
&lt;em&gt;the text form is only a second-class convenience&lt;/em&gt;, and it is not the language itself.
The text syntax exists solely to cater to our foibles as humans for whom reading JSON directly is annoying.
Bril itself is the JSON format you see above.
But as a concession to our foibles, among Bril&amp;rsquo;s many tools are a &lt;a href=&quot;https://github.com/sampsyo/bril/blob/main/bril-txt/briltxt.py&quot;&gt;parser and pretty-printer&lt;/a&gt;.
Here&amp;rsquo;s the text form of the program above:&lt;/p&gt;

&lt;div class=&quot;language-bril highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;@main&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As a consequence, working with Bril means typing commands like this a lot:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ bril2json &amp;lt; program.bril | do_something | bril2txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It can get tedious to constantly convert to and from JSON,
and it&amp;rsquo;s wasteful to serialize and deserialize programs at each stage in a long pipeline.
But the trade-off is that the Bril ecosystem comprises a large number of small pieces, loosely joined and infinitely remixable on the command line.&lt;/p&gt;

&lt;h2 id=&quot;language-design-good-bad-and-ugly&quot;&gt;Language Design: Good, Bad, and Ugly&lt;/h2&gt;

&lt;p&gt;There are a few design decisions in the language itself that reflect Bril&amp;rsquo;s education-over-practicality priorities.
For instance, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print&lt;/code&gt; is a &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/core.html&quot;&gt;core opcode&lt;/a&gt; in Bril; I don&amp;rsquo;t think this would be a good idea in most compilers, but it makes it easy to write small examples.&lt;/p&gt;

&lt;p&gt;Another quirk is that Bril is &lt;em&gt;extremely&lt;/em&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/A-normal_form&quot;&gt;A-normal form&lt;/a&gt;, to the point that constants always have to go in their own instructions and get their own names.
To increment an integer, for example, you can&amp;rsquo;t do this:&lt;/p&gt;

&lt;div class=&quot;language-bril highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;incr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead, Bril code is full of one-off constant variables, like this:&lt;/p&gt;

&lt;div class=&quot;language-bril highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;incr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This more-ANF-than-ANF approach to constants is verbose to the point of silliness.
But it simplifies the way you write some basic IL traversals because you don&amp;rsquo;t have to worry about whether operands come from variables or constants.
For many use cases, you get to handle constants the same way you do any other instruction.
For teaching, I think the regularity is worth the silliness.&lt;/p&gt;

&lt;p&gt;Bril is extensible, in a loosey-goosey way.
The string-heavy JSON syntax means it&amp;rsquo;s trivial to add new opcodes and data types.
Beyond the &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/core.html&quot;&gt;core language&lt;/a&gt;, there are &amp;ldquo;official&amp;rdquo; extensions for &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/memory.html&quot;&gt;manually managed memory&lt;/a&gt;, &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/float.html&quot;&gt;floating-point numbers&lt;/a&gt;, a funky form of &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/spec.html&quot;&gt;speculation&lt;/a&gt; I use for teaching JIT principles, &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/import.html&quot;&gt;module imports&lt;/a&gt;, and &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/char.html&quot;&gt;characters&lt;/a&gt;.
While a &lt;em&gt;laissez faire&lt;/em&gt; approach to extensions has worked so far, it&amp;rsquo;s also a mess:
there&amp;rsquo;s no systematic way to tell which extensions a given program uses or which language features a given tool supports.
&lt;a href=&quot;https://github.com/sampsyo/bril/issues/38&quot;&gt;A more explicit approach to extensibility&lt;/a&gt; would make the growing ecosystem easier to manage.&lt;/p&gt;

&lt;p&gt;Finally, Bril does not require SSA.
There is &lt;a href=&quot;https://capra.cs.cornell.edu/bril/lang/ssa.html&quot;&gt;an SSA form&lt;/a&gt; that includes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phi&lt;/code&gt; instruction, but the language itself has mutable variables.
I wouldn&amp;rsquo;t recommend this strategy for any other IL, but it&amp;rsquo;s helpful for teaching for three big reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I want students to feel the pain of working with non-SSA programs before the course introduces SSA. This frustration can help motivate why SSA is the modern consensus.&lt;/li&gt;
  &lt;li&gt;The course includes a task where students &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2023fa/lesson/6/#tasks&quot;&gt;implement into-SSA and out-of-SSA transformations&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;It&amp;rsquo;s really easy to generate Bril code from frontend languages that have mutable variables. The alternative would be LLVM&amp;rsquo;s &lt;a href=&quot;https://llvm.org/doxygen/Mem2Reg_8cpp_source.html&quot;&gt;mem2reg&lt;/a&gt;/&amp;rdquo;just put all the frontend variables in memory&amp;rdquo; trick, but Bril avoids building memory into the core language for simplicity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, this aftermarket SSA retrofit has been a huge headache.
It has caused &lt;a href=&quot;https://github.com/sampsyo/bril/issues/108&quot;&gt;persistent problems with undefinedness&lt;/a&gt; and &lt;a href=&quot;https://github.com/sampsyo/bril/issues/330&quot;&gt;classic correctness problems when translating out of SSA&lt;/a&gt;.
I think my original design is fundamentally flawed;
it was a mistake to treat &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phi&lt;/code&gt; semantically as &amp;ldquo;just another instruction&amp;rdquo; instead of a more invasive change to the language.
Bril&amp;rsquo;s SSA form needs a full rework, probably including an actual language extension along the lines of &lt;a href=&quot;https://mlir.llvm.org/docs/Rationale/Rationale/#block-arguments-vs-phi-nodes&quot;&gt;MLIR&amp;rsquo;s basic block arguments&lt;/a&gt;.
It has been an interesting lesson for me that SSA comes with subtle design implications that are difficult to retrofit onto an existing mutation-oriented IL.&lt;/p&gt;

&lt;h2 id=&quot;the-bril-ecosystem&quot;&gt;The Bril Ecosystem&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/bril/ecosystem.svg&quot; class=&quot;img-responsive bonw&quot; style=&quot;max-width: 450px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I cobbled together the first version of Bril in a hurry in the weeks before the fall 2019 semester began.
Since then, via the &amp;ldquo;open-source class&amp;rdquo; nature of &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2023fa/&quot;&gt;CS 6120&lt;/a&gt;, students have contributed a host of tools for working with the language.
The diagram above shows a sampling of what is in &lt;a href=&quot;https://github.com/sampsyo/bril&quot;&gt;the monorepo&lt;/a&gt;;
empty boxes are things I made and shaded boxes are things students contributed.
One &lt;a href=&quot;https://agentcooper.io&quot;&gt;satisfied CS 6120 customer&lt;/a&gt;
built a snazzy &lt;a href=&quot;https://agentcooper.github.io/bril-playground/&quot;&gt;web playground&lt;/a&gt; that I find super impressive.
You can find many more random tools by &lt;a href=&quot;https://github.com/search?q=bril+compiler&amp;amp;type=repositories&quot;&gt;searching on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the language extensions I mentioned were contributed by CS 6120 students.
In the run-up to the first semester, I was low on time and left memory, function calls, and floating-point numbers as &amp;ldquo;exercises for the reader.&amp;rdquo;
You can read 2019 blog posts &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/manually-managed-memory/&quot;&gt;by Drew Zagieboylo &amp;amp; Ryan Doenges about the memory extension&lt;/a&gt;,
&lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/function-calls/&quot;&gt;by Alexa VanHattum &amp;amp; Gregory Yauney about designing function calls&lt;/a&gt;,
and &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/floats-static-arrays/&quot;&gt;by Dietrich Geisler about floats&lt;/a&gt;.
Laziness can pay off.&lt;/p&gt;

&lt;p&gt;Please &lt;a href=&quot;https://discuss.systems/@adrian&quot;&gt;get in touch&lt;/a&gt; if you&amp;rsquo;re using Bril for something unconventional!
I love learning about the weird places it has gone.&lt;/p&gt;

</content>
    <summary type="html">&lt;p&gt;I created a new intermediate language, called &lt;a href=&quot;https://capra.cs.cornell.edu/bril/&quot;&gt;Bril&lt;/a&gt;, for teaching my funky open-source, hands-on compilers course.
Because it&amp;rsquo;s for education, Bril prioritizes simplicity and regularity over more typical compiler priorities like performance and concision.
This is an overview of Bril&amp;rsquo;s design, its quirks, and the ecosystem that has grown up around it since 2019.&lt;/p&gt;

</summary>
  </entry>
  
  <entry>
    
    <id>https://www.cs.cornell.edu/~asampson/blog/autoreduction.html</id>
    
    <title type="html">Automated Test-Case Reduction</title>
    <updated>2024-07-15T00:00:00+00:00</updated>
    <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/blog/autoreduction.html"/>
    <content type="html">&lt;aside&gt;
This post is the second in a series on &lt;em&gt;research skills&lt;/em&gt;.
The plan is to demonstrate techniques that &amp;ldquo;everyone knows&amp;rdquo; because everyone, in fact, does not already know them.
&lt;/aside&gt;

&lt;p&gt;&lt;a href=&quot;/~asampson/blog/reduction.html&quot;&gt;Last time&lt;/a&gt;, we saw how deleting stuff from a test case can be an easy and fun route to the root cause of a bug.
It&amp;rsquo;s less easy and less fun when the test cases get big.
The inner loop of test-case reduction can get old quickly:
delete stuff, run the special command, check the output to decide whether to backtrack or proceed.
It&amp;rsquo;s rote, mechanical, and annoyingly error prone.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s make the computer do it instead.
&lt;em&gt;Automated test-case reducers&lt;/em&gt; in the &lt;a href=&quot;https://github.com/csmith-project/creduce&quot;&gt;C-Reduce&lt;/a&gt; mold follow essentially the same &amp;ldquo;algorithm&amp;rdquo; we saw last time.
They obviously don&amp;rsquo;t know your bug like you do, so they can&amp;rsquo;t apply the intuition you might bring to deciding which things to delete when.
In return, automation can blindly try stuff much faster than a human can, potentially even in parallel.
So the trade-off is often worthwhile.&lt;/p&gt;

&lt;h2 id=&quot;automating-the-reduction-process&quot;&gt;Automating the Reduction Process&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;/~asampson/blog/reduction.html&quot;&gt;manual test-case reduction &amp;ldquo;algorithm,&amp;rdquo;&lt;/a&gt; there are really only three parts that entailed any human judgment:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Picking a part of the test case to delete.&lt;/li&gt;
  &lt;li&gt;Looking at the command&amp;rsquo;s output to decide whether we need to backtrack:
that is, detecting when a given deletion either removed the bug-triggering code or broke the program entirely.&lt;/li&gt;
  &lt;li&gt;Deciding when to give up: when we probably can&amp;rsquo;t reduce any farther without ruining the test case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else&amp;mdash;running the command after every edit, hitting &amp;ldquo;undo&amp;rdquo; after we decide to backtrack&amp;mdash;was pretty clearly mechanical.
Automated reducers take control of #1 and #3.
Picking the code to delete (#1) is just guesswork anyway; because we rely on step #2 to detect cases where a deletion went awry, it suffices to guess wildly using a pile of heuristics that have &lt;em&gt;some&lt;/em&gt; probability of finding a useful deletion.
To decide when to stop (#3), reducers detect a fixed point:
they give up when the heuristics fail to find any more code they can safely delete.&lt;/p&gt;

&lt;p&gt;That leaves us with #2: deciding whether a given version of the test case still works to reproduce the bug you&amp;rsquo;re interested in.
Test-case reducers call this the &lt;em&gt;interestingness test&lt;/em&gt; (in the &lt;a href=&quot;https://github.com/csmith-project/creduce&quot;&gt;C-Reduce&lt;/a&gt; tradition), and they typically want you to write it down as a shell script.
To use a reducer, then, you usually just need two ingredients:
the original test case you want to reduce and the interestingness test script.&lt;/p&gt;

&lt;h2 id=&quot;writing-an-interestingness-test&quot;&gt;Writing an Interestingness Test&lt;/h2&gt;

&lt;p&gt;This post will demonstrate how to use the excellent &lt;a href=&quot;https://github.com/DRMacIver/shrinkray&quot;&gt;Shrinkray&lt;/a&gt; reducer to debug &lt;a href=&quot;/~asampson/blog/reduction.html&quot;&gt;the same interpreter bug as last time&lt;/a&gt;.
Shrinkray is language-neutral&amp;ndash;and, in any case, no reducer knows about &lt;a href=&quot;https://capra.cs.cornell.edu/bril/&quot;&gt;Bril&lt;/a&gt;.
I think it&amp;rsquo;s pretty cool that reducers can work well even if they don&amp;rsquo;t know anything about the syntax or semantics of the language they&amp;rsquo;re working with.&lt;/p&gt;

&lt;p&gt;Like any automated reducer, Shrinkray needs an interestingness test:
a little program that checks whether the bug we care about currently exists.
To make one, &amp;ldquo;all we need to do&amp;rdquo; is take the commands we were running manually to check for the bug and put them in a shell script.&lt;/p&gt;

&lt;p&gt;The problem, as we&amp;rsquo;ll discover, is that Shrinkray is a little like &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Sorcerer&apos;s_Apprentice&quot;&gt;the sorcerer&amp;rsquo;s apprentice&lt;/a&gt;.
It will do exactly what we tell it to do, and it will do it with great enthusiasm.
Even tiny gaps in our instructions can lead to surprising results.
So, in my experience, writing a good interestingness test requires (a) a small bag of tricks that may not be intuitive your first time around, and (b) some trial and error.&lt;/p&gt;

&lt;p&gt;I recorded a video of my own trial-end-error process, and I&amp;rsquo;ve also written it out in prose below.
Here&amp;rsquo;s the video:&lt;/p&gt;

&lt;div class=&quot;embed&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/J06BU6Fj6Qs?si=92qpmGfvC56p206E&amp;amp;start=86&amp;amp;end=101&amp;amp;rel=0&quot; allow=&quot;picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Hang on; I&amp;rsquo;m being told that this is the wrong video.
Let&amp;rsquo;s try that again:&lt;/p&gt;

&lt;div class=&quot;embed&quot;&gt;
  &lt;iframe src=&quot;https://cdnapisec.kaltura.com/p/520801/sp/52080100/embedIframeJs/uiconf_id/31230141/partner_id/520801?iframeembed=true&amp;amp;entry_id=1_w0bwzism&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;walkthrough&quot;&gt;Walkthrough&lt;/h2&gt;

&lt;p&gt;In essence, an interestingness test is a script that automates the
commands we ran repetitively (with the &amp;ldquo;up arrow&amp;rdquo; at the shell prompt) during
&lt;a href=&quot;/~asampson/blog/reduction.html&quot;&gt;manual reduction&lt;/a&gt;.
Here&amp;rsquo;s the main command we repeatedly ran then:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ bril2json &amp;lt; problem.bril | cargo run -- -p false false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I started by just putting this command into a shell script, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interesting.sh&lt;/code&gt;.
&lt;a href=&quot;https://github.com/DRMacIver/shrinkray&quot;&gt;Shrinkray&lt;/a&gt; wants our script to work from any directory, so I used an absolute
path to the executable:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
bril2json &amp;lt; $1 | /Users/fabian/Documents/cu/bril/brilirs/target/debug/brilirs -p false false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$1&lt;/code&gt; for the filename; Shrinkray passes the current version of
the test file as an argument there.
Following the &lt;a href=&quot;https://github.com/csmith-project/creduce&quot;&gt;C-Reduce&lt;/a&gt; tradition, interestingness tests use a
counter-intuitive (but extremely useful) convention:
the script must exit with status 0 when the test is &lt;em&gt;interesting&lt;/em&gt; (exhibits
the bug) and nonzero when it&amp;rsquo;s not (e.g., it&amp;rsquo;s bug-free or ill-formed).
So far, our command crashes with a nonzero status&amp;mdash;specifically, a Rust
panic&amp;mdash;when it &lt;em&gt;is&lt;/em&gt; interesting, which is the opposite of what we want.&lt;/p&gt;

&lt;h3 id=&quot;trick-1-shell-negation&quot;&gt;Trick 1: Shell Negation&lt;/h3&gt;

&lt;p&gt;So here&amp;rsquo;s a trick you can use in interestingness tests in general:
try the
&lt;a href=&quot;https://www.gnu.org/software/bash/manual/html_node/Pipelines.html&quot;&gt;shell&amp;rsquo;s negation operator, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt;,&lt;/a&gt; to invert the sense of the exit status.
Like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
! ( bril2json &amp;lt; $1 | /Users/fabian/Documents/cu/bril/brilirs/target/debug/brilirs -p false false )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now our tests says the input is interesting (status 0) when the interpreter
&lt;em&gt;does&lt;/em&gt; crash.
Let&amp;rsquo;s try it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ shrinkray interesting.sh problem.bril
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this script, Shrinkray immediately reduces our file down to nothing.
And it&amp;rsquo;s not wrong: a zero-byte file does elicit an error from our
interpreter!
As usual, the sorcerer&amp;rsquo;s apprentice did exactly what we said, not what
we wanted, and did it with wondrous alacrity.&lt;/p&gt;

&lt;h3 id=&quot;trick-2-the-not-bogus-check-and-set--e&quot;&gt;Trick 2: The &amp;ldquo;Not Bogus&amp;rdquo; Check and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set -e&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;This leads us to our second trick for writing interestingness tests:
before you try to expose the bug, include a &amp;ldquo;not bogus&amp;rdquo; check.
You want to make sure your test is well-formed in some sense:
it parses successfully,
or it doesn&amp;rsquo;t throw an exception,
or whatever else.&lt;/p&gt;

&lt;p&gt;In our case, we&amp;rsquo;re debugging a crashy interpreter, but we also have access to
a &lt;a href=&quot;https://capra.cs.cornell.edu/bril/tools/interp.html&quot;&gt;reference interpreter&lt;/a&gt; that doesn&amp;rsquo;t have the same bug.
So we can add a &amp;ldquo;not bogus&amp;rdquo; check that requires any well-formed test to
succeed in that reference interpreter.
Here&amp;rsquo;s a new script that includes that check:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
set -e
bril2json &amp;lt; problem.bril | brili false false
! ( bril2json &amp;lt; $1 | /Users/fabian/Documents/cu/bril/brilirs/target/debug/brilirs -p false false )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check out that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set -e&lt;/code&gt; line, which is super useful for interestingness
scripts and &amp;ldquo;not bogus&amp;rdquo; checks in particular.
&lt;a href=&quot;https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html&quot;&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e&lt;/code&gt; option&lt;/a&gt; makes a shell script immediately abort with an
error (signalling &amp;ldquo;uninteresting&amp;rdquo;) when any line fails.
In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e&lt;/code&gt; mode, you&amp;rsquo;re free to write a bunch of commands that should succeed in
the normal case but might fail when the test really goes off the rails:
as soon as any command fails, the script will bail out and indicate that we&amp;rsquo;re
on the wrong path.
So in my script here, we&amp;rsquo;re requiring the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brili&lt;/code&gt; (reference interpreter)
invocation to succeed before we even bother trying to expose the
bug.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s restore our test input from the backup Shrinkray saved for us and try
again:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cp problem.bril.bak problem.bril
$ shrinkray interesting.sh problem.bril
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Shrinkray will again sorcerer&amp;rsquo;s-apprentice its way into a much too small test
case.
Not zero bytes this time, but too small to be useful.
To see what went wrong, we can run our two commands (&amp;ldquo;not bogus&amp;rdquo; and bug
check) manually:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ bril2json &amp;lt; problem.bril | brili false false
$ bril2json &amp;lt; problem.bril | ./target/debug/brilirs -p false false
error: Expected a primitive type like int or bool, found l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Shrinkray has isolated for us a &lt;em&gt;different error&lt;/em&gt; with the same shape, i.e.,
the reference interpreter accepts the program but the buggy interpreter
rejects it.
This one is not really a bug: it&amp;rsquo;s just a case where the second interpreter is
more strict than the first.
And even if this misalignment is curious, it&amp;rsquo;s not the bug we were looking
for.&lt;/p&gt;

&lt;h3 id=&quot;trick-3-grep-for-your-error-message&quot;&gt;Trick 3: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; for Your Error Message&lt;/h3&gt;

&lt;p&gt;So here&amp;rsquo;s trick number three for writing interestingness tests:
use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; to check for the error message we actually want.
In our case, the program panics with an &amp;ldquo;out of bounds&amp;rdquo; message.
Let&amp;rsquo;s restore from backup and grep for that in both stdout and stderr like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cp problem.bril.bak problem.bril
$ bril2json &amp;lt; problem.bril | ./target/debug/brilirs -p false false 2&amp;gt;&amp;amp;1 | grep &apos;out of bounds&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Conveniently, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; does exactly what you want for an interestingness test:
its exit status is 0 when it finds the string (interesting because this is the
bug we are looking for)
and 1 when it fails to find the string (some other error).
So here&amp;rsquo;s our new script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
set -e
bril2json &amp;lt; problem.bril | brili false false
bril2json &amp;lt; $1 | /Users/fabian/Documents/cu/bril/brilirs/target/debug/brilirs -p false false 2&amp;gt;&amp;amp;1 | grep &apos;out of bounds&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this script, Shrinkray does a great job and reduces quite a bit of code,
which is awesome.
One thing it can&amp;rsquo;t reduce, however, is the way the program uses arguments.
It&amp;rsquo;s actually hopeless for Shrinkray to remove the arguments because it would
require changing the interestingness test script to remove those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false false&lt;/code&gt;
command-line arguments we keep on passing.&lt;/p&gt;

&lt;h3 id=&quot;trick-4-occasional-manual-help&quot;&gt;Trick 4: Occasional Manual Help&lt;/h3&gt;

&lt;p&gt;We can help Shrinkray out a bit by turning those inputs into constants in the code.
We can replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;&amp;rsquo;s arguments with two new Bril instructions:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;b0: bool = const false;
b1: bool = const false;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Accordingly, we need to change our interestingness test to pass zero arguments
to both interpreter invocations:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
set -e
bril2json &amp;lt; problem.bril | brili
bril2json &amp;lt; $1 | /Users/fabian/Documents/cu/bril/brilirs/target/debug/brilirs -p 2&amp;gt;&amp;amp;1 | grep &apos;out of bounds&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, it&amp;rsquo;s probably a good idea to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./interesting.sh problem.bril&lt;/code&gt;
to manually check that everything&amp;rsquo;s in order.&lt;/p&gt;

&lt;h3 id=&quot;success&quot;&gt;Success!&lt;/h3&gt;

&lt;p&gt;Everything looks good in this case, so we can fire up Shrinkray again.
It gives us this reduced test case:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@main{
    jmp .A;
    .A:ret;
    .A:jmp  .A;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is not quite the same as the reduced version we arrived at with &lt;a href=&quot;/~asampson/blog/reduction.html&quot;&gt;manual
reduction&lt;/a&gt;.
The one we wrote found last time actually an infinite loop&amp;mdash;so it wouldn&amp;rsquo;t
pass our interestingness test!
So this one is actually a little more useful in that sense: you can run it
through the reference interpreter to immediately see what it&amp;rsquo;s &lt;em&gt;supposed&lt;/em&gt; to
do.
It&amp;rsquo;s sort of weird that the reduced test has a multiply defined label, but
apparently neither interpreter cares about that&amp;hellip;
if we wanted to, we could imagine avoiding this by adding a second &amp;ldquo;not bogus&amp;rdquo;
check based on some static error checking.&lt;/p&gt;

</content>
    <summary type="html">&lt;p&gt;In a &lt;a href=&quot;{{site.base}}/blog/reduction.html&quot;&gt;previous post&lt;/a&gt;, I used a simple interpreter bug to demonstrate the research skill of manually reducing test cases.
This time, I show off the excellent &lt;a href=&quot;https://github.com/DRMacIver/shrinkray&quot;&gt;Shrinkray&lt;/a&gt; reducer to see how it can automate the same process.
The tricky part when using automated test-case reducers is writing an &lt;em&gt;interestingness test&lt;/em&gt; that actually does what you want.
I list a few tricks that help me write good interestingness tests.&lt;/p&gt;

</summary>
  </entry>
  
  <entry>
    
    <id>https://www.cs.cornell.edu/~asampson/blog/flatgfa.html</id>
    
    <title type="html">One Weird Trick for Efficient Pangenomic Variation Graphs (and File Formats for Free)</title>
    <updated>2024-05-28T00:00:00+00:00</updated>
    <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/blog/flatgfa.html"/>
    <content type="html">&lt;p&gt;We built an efficient binary representation for &lt;a href=&quot;/~asampson/blog/mygfa.html&quot;&gt;pangenomic variation graphs&lt;/a&gt; that is equivalent to the standard &lt;a href=&quot;https://github.com/GFA-spec/GFA-spec&quot;&gt;GFA text format&lt;/a&gt;.
Our approach isn&amp;rsquo;t at all novel, but it illustrates a fun way that you can start with a naïve representation and transform it into a fast in-memory representation while also getting an on-disk file format for free.
In a shamelessly cherry-picked scenario, our tool is 1,331&amp;times; faster than &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;an existing, optimized pangenomics toolkit&lt;/a&gt; that already uses an efficient binary representation.&lt;/p&gt;

&lt;h2 id=&quot;pangenome-recap&quot;&gt;Pangenome Recap&lt;/h2&gt;

&lt;p&gt;Let&amp;rsquo;s return to that pangenome data structure &lt;a href=&quot;/~asampson/blog/mygfa.html&quot;&gt;from last time&lt;/a&gt;. Here&amp;rsquo;s a simplified part of the data model, in fake Rust:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/pointery.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Graph&lt;/code&gt; owns sequences of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment&lt;/code&gt;s and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;s, and the latter contains a sequence of &lt;em&gt;steps&lt;/em&gt;.
Each step is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handle&lt;/code&gt; that encapsulates a traversal of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment&lt;/code&gt; in either the forward to the backward direction.
In the &lt;a href=&quot;https://github.com/GFA-spec/GFA-spec&quot;&gt;GFA&lt;/a&gt; text format, here are three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment&lt;/code&gt;s and one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;S	1	CAAATAAG
S	2	AAATTTTCTGGAGTTCTAT
S	4	CCAACTCTCTG
P	x	1+,2+,4-	*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; lines are segments, which contain short nucleotide sequences.
That &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P&lt;/code&gt; line is a path with three steps:
it traverses segment 1 and 2 in the forward (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;) direction and then segment 4 in the backward (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;) direction.
It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/xpath.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This data structure seems pretty pointery.
&lt;a href=&quot;/~asampson/blog/mygfa.html&quot;&gt;Last time&lt;/a&gt;, I showed off a straightforward &lt;a href=&quot;https://cucapra.github.io/pollen/mygfa/&quot;&gt;Python library&lt;/a&gt; we made that embraces that pointeriness.
It&amp;rsquo;s slow but clear.&lt;/p&gt;

&lt;p&gt;This post is about implementing an efficient representation.
Other efficient data representations exist&amp;mdash;prominently, &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;odgi&lt;/a&gt;, which is by some collaborators.
But they get performance by combining many different representation tricks.
I want to understand the basics here by using a single principle and see how far it gets us.&lt;/p&gt;

&lt;h2 id=&quot;flattening-the-data-structures&quot;&gt;Flattening the Data Structures&lt;/h2&gt;

&lt;p&gt;The central principle we&amp;rsquo;ll use is &lt;a href=&quot;/~asampson/blog/flattening.html&quot;&gt;flattening&lt;/a&gt;, a.k.a. arena allocation, a.k.a. just cramming everything into arrays and using indices instead of pointers.
In the fake Rust declarations above, I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ref&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; to suggest ordinary pointer-based references to one and many elements.
In the flattened version, we&amp;rsquo;ll replace all of those those with plain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt;s:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/indexy.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now the central &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Graph&lt;/code&gt; struct has three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; arenas that own all the segments, paths, and the steps within the paths.
Instead of a direct reference to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handle&lt;/code&gt; struct has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt; index into the segment arena.
And each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt; refers to a contiguous range in the step arena with start/end indices.
In &lt;a href=&quot;https://github.com/cucapra/pollen/blob/main/flatgfa/src/flatgfa.rs&quot;&gt;the real thing&lt;/a&gt;, even &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path::name&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment::sequence&lt;/code&gt; get the same treatment:
there are two &lt;em&gt;giant&lt;/em&gt; byte strings in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Graph&lt;/code&gt; struct that act as arenas;
every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Segment&lt;/code&gt; just has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u32, u32)&lt;/code&gt; pair to refer to its name or sequence as a chunk within a given string.&lt;/p&gt;

&lt;p&gt;The result is that, outside of the arenas, all the types involved are fixed-size, smallish, pointer-free structs.
It might be helpful to visualize the memory layout:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/memory.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path::steps&lt;/code&gt; field refers to a slice of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path_steps&lt;/code&gt; array, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handle::segment&lt;/code&gt; field in there refers to a position in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;segments&lt;/code&gt; array.
There are no actual, word-sized pointers to the virtual address space anywhere.
Again, while I put the path names and the nucleotide sequences inline to make this picture simpler, &lt;a href=&quot;https://github.com/cucapra/pollen/blob/main/flatgfa/src/flatgfa.rs&quot;&gt;the actual implementation&lt;/a&gt; stores those in yet more arenas.
My current implementation uses 12 arenas to implement the complete GFA data model.&lt;/p&gt;

&lt;h2 id=&quot;its-pretty-fast-i-guess&quot;&gt;It&amp;rsquo;s Pretty Fast, I Guess&lt;/h2&gt;

&lt;p&gt;What have we really gained by replacing all our pointers with integers?
Even though we haven&amp;rsquo;t fundamentally changed the data structure, there are a few reasons why a flat representation for GFAs should be more efficient:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Faster allocation. By sacrificing the ability to deallocate elements at a fine granularity, we can use simple bump allocation instead of a proper &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; when building the data structure.&lt;/li&gt;
  &lt;li&gt;Locality. We&amp;rsquo;re forcing logically contiguous elements, such as each path&amp;rsquo;s steps, to be contiguous in memory. That&amp;rsquo;s probably good for spatial locality.&lt;/li&gt;
  &lt;li&gt;Smaller pointers. Replacing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;Segment&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt; comes with a 2&amp;times; space savings on 64-bit machines. In a data structure with so many internal references, that probably counts for something.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here&amp;rsquo;s a brutally unfair way to measure the bottom-line performance impact of these effects.
We can compare our new, flattened implementation&amp;mdash;called &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/flatgfa&quot;&gt;FlatGFA&lt;/a&gt; and implemented in Rust&amp;mdash;against our simple-as-possible &lt;a href=&quot;https://cucapra.github.io/pollen/mygfa/&quot;&gt;Python library&lt;/a&gt; &lt;a href=&quot;/~asampson/blog/mygfa.html&quot;&gt;from last time&lt;/a&gt;.
For more context, we can also compare against &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;odgi&lt;/a&gt;, a C++ toolkit for GFA processing that &lt;em&gt;also&lt;/em&gt; uses an efficient, index-based representation internally.&lt;/p&gt;

&lt;p&gt;For this experiment, we&amp;rsquo;ll just compare the time to &lt;em&gt;round-trip&lt;/em&gt; a GFA file through the internal representation:
we&amp;rsquo;ll make each tool parse and then immediately pretty-print the GFA to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/null&lt;/code&gt;.&lt;sup id=&quot;fnref:norm&quot;&gt;&lt;a href=&quot;#fn:norm&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
I measured the round-trip performance on three tiny graphs and three medium-sized ones from the &lt;a href=&quot;https://humanpangenome.org&quot;&gt;Human Pangenome Reference Consortium&lt;/a&gt; and the &lt;a href=&quot;https://github.com/AndreaGuarracino/1000G-ONT-F100-PGGB&quot;&gt;1000 Genomes Project&lt;/a&gt;.&lt;sup id=&quot;fnref:sys&quot;&gt;&lt;a href=&quot;#fn:sys&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;div class=&quot;figrow&quot;&gt;
&lt;figure style=&quot;width: 55%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/roundtrip-mini.svg&quot; class=&quot;bonw&quot; alt=&quot;A bar chart comparing three tools&apos; time to round-trip GFA files through their in-memory representations. This comparison uses small graphs.&quot; /&gt;
&lt;figcaption&gt;Time to round-trip (parse and pretty-print) some tiny GFA files (288&amp;nbsp;kB, 1.0&amp;nbsp;MB, and 1.5&amp;nbsp;MB, from left to right). Our pure-Python &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/slow_odgi&quot;&gt;slow-odgi&lt;/a&gt; library is about 2&amp;times; slower than odgi.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;max-width: 42%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/roundtrip-med.svg&quot; class=&quot;bonw&quot; alt=&quot;A bar chart comparing two fast tools doing the same GFA round-trip task on much larger files.&quot; /&gt;
&lt;figcaption&gt;Round-tripping some bigger GFAs (7.2&amp;nbsp;GB, 2.3&amp;nbsp;GB, and 2.7&amp;nbsp;GB). The pure-Python library is not a contender. FlatGFA is 11.3&amp;times; faster than odgi on average (harmonic mean).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;FlatGFA can round-trip small GFA files about 14&amp;times; faster than &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/slow_odgi&quot;&gt;slow-odgi&lt;/a&gt;.
That speedup conflates the three fundamental advantages above with mundane implementation differences (FlatGFA is in Rust; slow-odgi is in Python).
I would love to do more measurement work here to disentangle these effects:
for example, we could check how much the pointer size matters by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt;s everywhere instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;FlatGFA is also 11.3&amp;times; faster than &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;odgi&lt;/a&gt; on average.&lt;sup id=&quot;fnref:fastest&quot;&gt;&lt;a href=&quot;#fn:fastest&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
It can process the largest graph (7.2&amp;nbsp;GB of uncompressed GFA text) in 67 seconds, versus 14 minutes for odgi.
I don&amp;rsquo;t really know why, because odgi also (sensibly) uses a mostly flattened representation.
My best guess is that odgi&amp;rsquo;s implementers focused their efforts on actually novel, actually smart data structure ideas with asymptotic benefits (e.g., they use a special index to quickly locate steps within a path) and not on boring data-representation engineering that only brings constant factors.
FlatGFA only does boring, but it goes all the way: it exterminates &lt;em&gt;all&lt;/em&gt; the pointers.
And the constant-factor payoff from this basic flattening transformation can be surprisingly large.&lt;/p&gt;

&lt;p&gt;Possible lessons here include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&amp;ldquo;Merely&amp;rdquo; flattening an otherwise naïve data structure can turn it into a competitive one.&lt;/li&gt;
  &lt;li&gt;So it can be helpful to get that in place even before seeking asymptotic gains through indexing and the like.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;a-file-format-for-free&quot;&gt;A File Format for Free&lt;/h2&gt;

&lt;p&gt;Because it has no pointers in it, a ruthlessly flattened in-memory representation has one bonus feature:
it does double duty as a file format.
If we take all those densely packed arrays-of-structs that make up a FlatGFA, concatenate them together, and add a little header to contain the sizes, we have a blob of bytes that we might as well write to disk.&lt;/p&gt;

&lt;p&gt;Turning FlatGFA into a file format took two steps:
applying the amazing &lt;a href=&quot;https://docs.rs/zerocopy/&quot;&gt;zerocopy&lt;/a&gt; crate,
and separating the data store from the interface to the data.&lt;/p&gt;

&lt;h3 id=&quot;use-zerocopy-to-get-asbytes-and-frombytes&quot;&gt;Use zerocopy to get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsBytes&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FromBytes&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;First, &lt;a href=&quot;https://docs.rs/zerocopy/&quot;&gt;zerocopy&lt;/a&gt; is an immensely rad crate that can certify that it&amp;rsquo;s safe to cast between Rust types and raw bytes.
It contains a bunch of macros, but these macros don&amp;rsquo;t actually generate code for you: they just check that your types obey a bunch of rules.
The zerocopy authors have done the hard work (i.e., thinking carefully and using &lt;a href=&quot;https://github.com/rust-lang/miri&quot;&gt;Miri&lt;/a&gt;) to be pretty confident that all types that obey their rules can be safely transmuted to and from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[u8]&lt;/code&gt; chunks.
These rules include alignment restrictions and the necessity that every bit-pattern is a valid value.
The latter rule makes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt;s tricky: every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt; must have 2&lt;em&gt;ⁿ&lt;/em&gt; variants where &lt;em&gt;n&lt;/em&gt; is the number of bits in its representation.
But once you&amp;rsquo;ve cleared all those hurdles, your types gain the &lt;a href=&quot;https://docs.rs/zerocopy/latest/zerocopy/trait.AsBytes.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsBytes&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FromBytes&lt;/code&gt;&lt;/a&gt; traits.&lt;/p&gt;

&lt;h3 id=&quot;separate-the-storage-from-the-interface&quot;&gt;Separate the Storage from the Interface&lt;/h3&gt;

&lt;p&gt;Now that all our structs are equipped with the zerocopy superpowers, we need a way to make &lt;em&gt;the entire data structure&lt;/em&gt; map to bytes.
One nice way to do it is to separate our actual data storage location from a lightweight view of all the same data.
The idea is to start with that top-level struct that contains nothing but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;s of littler structs:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/store1.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And to separate it into two structs.
We want one &lt;em&gt;store&lt;/em&gt; object that keeps all those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;s and one &lt;em&gt;view&lt;/em&gt; object that has all the same fields but with slices instead of vectors:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/store2.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThingStore&lt;/code&gt; struct will never get the zerocopy superpower&amp;mdash;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;s are inherently pointerful and must live on the heap&amp;mdash;but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThingView&lt;/code&gt; is perfectly suited.
We can construct one by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from_bytes&lt;/code&gt; on different chunks within a big byte buffer:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/store3.svg&quot; class=&quot;img-responsive bonw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We&amp;rsquo;ll also need a small &lt;a href=&quot;https://github.com/cucapra/pollen/blob/788aa3e48aff6a8c7b46f10c1c5fcaeee909518b/flatgfa/src/file.rs#L10-L26&quot;&gt;table of contents&lt;/a&gt; at the top of the file to tell us where those chunks are.
But once we&amp;rsquo;ve managed that, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThingView&lt;/code&gt; serves as an abstraction layer over the two storage styles.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;-based store provides heap-allocated, arbitrarily resizable allocation pools;
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; option constrains the sizes of the arenas but maps easily to a flat file.&lt;sup id=&quot;fnref:slicevec&quot;&gt;&lt;a href=&quot;#fn:slicevec&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;0-copy---speedup&quot;&gt;0 Copy = ∞ Speedup&lt;/h2&gt;

&lt;p&gt;Using the same type for the in-memory and on-disk representations is not just convenient;
it also makes deserialization &lt;em&gt;infinity times faster&lt;/em&gt;.&lt;sup id=&quot;fnref:capnproto&quot;&gt;&lt;a href=&quot;#fn:capnproto&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;
To &amp;ldquo;open&amp;rdquo; a file, all we need to do is to &lt;a href=&quot;https://linux.die.net/man/2/mmap&quot;&gt;mmap(2)&lt;/a&gt; it.
There is no deserialization step; we don&amp;rsquo;t even have to read the whole file if we don&amp;rsquo;t need it.&lt;/p&gt;

&lt;p&gt;This latter aspect can lead to some pretty funny speedups for FlatGFA compared to odgi, which has its own efficient binary GFA-equivalent file format but which uses traditional serialization.
Here&amp;rsquo;s a performance comparison between &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/rst/commands/odgi_paths.html&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;odgi paths -L&lt;/code&gt; command&lt;/a&gt;, which just prints out all the names of the paths in the graph, and the FlatGFA and slow-odgi equivalents:&lt;/p&gt;

&lt;div class=&quot;figrow&quot;&gt;
&lt;figure style=&quot;width: 55%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/paths-mini.svg&quot; class=&quot;bonw&quot; alt=&quot;A bar chart comparing three tools&apos; times to print out a list of path names from GFA files.&quot; /&gt;
&lt;figcaption&gt;Time to print the path names from the same graphs as above. The &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/slow_odgi&quot;&gt;slow-odgi&lt;/a&gt; reference implementation is a hilarious 19&amp;times; slower than odgi on average.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;max-width: 40%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/paths-med.svg&quot; class=&quot;bonw&quot; alt=&quot;Another bar chart comparing the same path-name-printing task on three larger GFAs, and only comparing the two faster tools.&quot; /&gt;
&lt;figcaption&gt;Printing paths names from the same larger graphs. FlatGFA is 1,331&amp;times; faster than odgi, which is not infinity, but it&apos;s pretty good.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;This comparison starts with the native binary formats for odgi and FlatGFA, so only slow-odgi actually has to parse any text.
Across the three big GFAs, FlatGFA is 1,331&amp;times; faster than odgi on average.
On the largest (7.2&amp;nbsp;GB) graph, FlatGFA takes 5.8&amp;nbsp;ms to odgi&amp;rsquo;s 12&amp;nbsp;seconds.
If you look at &lt;a href=&quot;https://share.firefox.dev/3KiOKLW&quot;&gt;a profile of where odgi spends its time&lt;/a&gt;, about 90% of it goes to deserialization and 9.9% goes to deallocating that data structure.
Less than 1% of the time is spent on the &amp;ldquo;real work,&amp;rdquo; i.e., extracting and printing those path names.
So this is an extreme edge case where FlatGFA&amp;rsquo;s deserialization- and allocation-free strategy makes for especially silly-looking bar charts.&lt;/p&gt;

&lt;h2 id=&quot;someday-acceleration&quot;&gt;Someday, Acceleration&lt;/h2&gt;

&lt;p&gt;We originally fell into this rabbit hole because we want to build hardware accelerators for this stuff.
Surprising absolutely no one, data representation turns out to be the key to an efficient implementation, regardless of whether it&amp;rsquo;s in hardware or software.
Outside of flattening and memory-mapping, FlatGFA is totally unoptimized&amp;mdash;so we&amp;rsquo;re now fully distracted by understanding the space of possible efficient representations and their implications for hardware design.
We&amp;rsquo;ll get back to implementing that hardware someday.
I promise.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:norm&quot;&gt;
      &lt;p&gt;FlatGFA is the only one of the three tools that actually preserves GFA files, byte for byte, when round-tripping them. Both odgi and mygfa (quite sensibly) normalize the ordering of elements in the graph. I made FlatGFA preserve the contents exactly to make it easier to test.&amp;nbsp;&lt;a href=&quot;#fnref:norm&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:sys&quot;&gt;
      &lt;p&gt;All wall-clock times collected on our lab server, which has two Xeon Gold 6230 processors (20 cores per socket @ 2.1 GHz) and 512 GB RAM. It runs Ubuntu 22.04. Error bars show standard deviations over at least 3 (and usually more like 10) runs, measured with &lt;a href=&quot;https://github.com/sharkdp/hyperfine&quot;&gt;Hyperfine&lt;/a&gt;.&amp;nbsp;&lt;a href=&quot;#fnref:sys&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:fastest&quot;&gt;
      &lt;p&gt;I think this means FlatGFA currently has the fastest GFA parser in the universe. This is not all that impressive; it&amp;rsquo;s an extremely easy-to-parse grammar. Even so, I would love to be proven wrong.&amp;nbsp;&lt;a href=&quot;#fnref:fastest&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:slicevec&quot;&gt;
      &lt;p&gt;In &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/flatgfa&quot;&gt;the real implementation&lt;/a&gt;, I also added a second storage style based on &lt;a href=&quot;https://docs.rs/tinyvec/latest/tinyvec/struct.SliceVec.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tinyvec::SliceVec&lt;/code&gt;&lt;/a&gt; instead of plain old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;. This approach splits the difference between slices and vectors: each arena has a fixed maximum capacity, but its length can be less than that. So the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SliceVec&lt;/code&gt;s, even when they map to a fixed-size &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; of file contents, can still shrink and grow within limits.&amp;nbsp;&lt;a href=&quot;#fnref:slicevec&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:capnproto&quot;&gt;
      &lt;p&gt;This hyperbolic framing is stolen from &lt;a href=&quot;https://capnproto.org&quot;&gt;Cap&amp;rsquo;n Proto&lt;/a&gt;, which honestly blew my mind the first time I understood what it was doing.&amp;nbsp;&lt;a href=&quot;#fnref:capnproto&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
    <summary type="html">&lt;p&gt;&lt;a href=&quot;{{site.base}}/blog/mygfa.html&quot;&gt;Last time&lt;/a&gt;, I introduced &lt;a href=&quot;https://en.wikipedia.org/wiki/Pan-genome&quot;&gt;pangenomic variation graphs&lt;/a&gt;, the standard &lt;a href=&quot;https://github.com/GFA-spec/GFA-spec&quot;&gt;text file format&lt;/a&gt; that biologists use for them, and a &lt;a href=&quot;https://cucapra.github.io/pollen/mygfa/&quot;&gt;hopelessly naïve reference data model&lt;/a&gt; we implemented for them. This time, we use a single principle&amp;mdash;flattening&amp;ndash;to build an efficient representation that is not only way faster than the naïve library but also competitive with an &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;exisitng, optimized toolkit&lt;/a&gt;. Flattening also yields a memory-mapped file format &amp;ldquo;for free&amp;rdquo; that, in a shamelessly cherry-picked scenario, is more than a thousand times faster than the serialization-based alternative.&lt;/p&gt;

</summary>
  </entry>
  
  <entry>
    
    <id>https://www.cs.cornell.edu/~asampson/blog/mygfa.html</id>
    
    <title type="html">Pangenomic Variation Graphs, and a Reference Data Model</title>
    <updated>2024-04-30T00:00:00+00:00</updated>
    <link rel="alternate" href="https://www.cs.cornell.edu/~asampson/blog/mygfa.html"/>
    <content type="html">&lt;p&gt;Lately, we&amp;rsquo;ve been collaborating with some hip biologists who do something called &lt;a href=&quot;https://en.wikipedia.org/wiki/Pan-genome&quot;&gt;pangenomics&lt;/a&gt;, which is like regular genomics but cooler. In regular genomics, you sequence each organism&amp;rsquo;s genome by aligning it to a &lt;em&gt;reference genome&lt;/em&gt; that somebody previously assembled &lt;a href=&quot;https://en.wikipedia.org/wiki/De_novo_sequence_assemblers&quot;&gt;at great cost&lt;/a&gt;. In a sense, the traditional view models all of us as variations on &lt;a href=&quot;https://en.wikipedia.org/wiki/Reference_genome#Human_reference_genome&quot;&gt;a Platonic ideal of &lt;em&gt;Homo sapiens&lt;/em&gt;&lt;/a&gt;. Pangenomicists instead try to directly model the variation among an entire population. Instead of aligning reads from &lt;em&gt;N&lt;/em&gt; individuals against one common reference, the goal is to mutually align each of the &lt;em&gt;N&lt;/em&gt; against each other. This all-to-all comparison, they tell us, is the key to understanding a population&amp;rsquo;s diversity and revealing subtleties that are undetectable with the traditional approach. But it also scales up the computational cost for every computational step in a genomic analysis pipeline&amp;mdash;which aren&amp;rsquo;t exactly cheap even in the traditional, reference-based mode.&lt;/p&gt;

&lt;p&gt;A &lt;a href=&quot;https://github.com/vgteam/vg&quot;&gt;pangenome variation graph&lt;/a&gt; is the data structure these folks work with.
It models the genetic sequences that multiple individuals have in common and how they differ.
The graph&amp;rsquo;s vertices are little snippets of DNA sequences, and each individual is a walk through these vertices:
if you concatenate all the little DNA sequences along a given walk, you get the full genome sequence for the individual.
Here&amp;rsquo;s a picture of a &lt;a href=&quot;/~asampson/media/flatgfa/tiny.gfa&quot;&gt;tiny fake graph I made&lt;/a&gt;, drawn by &lt;a href=&quot;https://github.com/vgteam/vg&quot;&gt;the vg tool&lt;/a&gt; made by some of our collaborators:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/tiny.svg&quot; class=&quot;img-responsive&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Hilariously, vg picked the 🎷 and 🕌 emojis to represent the two walks in the graph, i.e., the two individual organisms in our little population.
(And &lt;a href=&quot;https://graphviz.org&quot;&gt;GraphViz&lt;/a&gt; has made something of a mess of things, which isn&amp;rsquo;t unusual.)
You can see the 🎷 genome going through segments 1, 2, and 4;
🕌 also takes a detour through segment 3, which is the nucleotide sequence TTG.
Just to make things a little more fun, these walks pass through each segment &lt;em&gt;directionally:&lt;/em&gt;
either forward, yielding the DNA sequence you see written in the node, or backward, yielding the sequence&amp;rsquo;s &lt;a href=&quot;http://genewarrior.com/docs/exp_revcomp.jsp&quot;&gt;reverse complement&lt;/a&gt;.
That&amp;rsquo;s what&amp;rsquo;s going on with segment 4 here: both paths traverse it backward.&lt;/p&gt;

&lt;p&gt;The pangenome folks have a standard file format for these variation graphs:
&lt;a href=&quot;https://github.com/GFA-spec/GFA-spec&quot;&gt;Graphical Fragment Assembly (GFA)&lt;/a&gt;.
GFA is a text format, and it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;S	1	CAAATAAG
S	2	AAATTTTCTGGAGTTCTAT
S	3	TTG
S	4	CCAACTCTCTG
P	x	1+,2+,4-	*
P	y	1+,2+,3+,4-	*
L	1	+	2	+	0M
L	2	+	4	-	0M
L	2	+	3	+	0M
L	3	+	4	-	0M
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This GFA file is the variation graph I drew above.
Each line declares some part of the graph.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; lines are &lt;em&gt;segments&lt;/em&gt; (vertices);
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P&lt;/code&gt; is for &lt;em&gt;path&lt;/em&gt; (which are those per-individual walks);
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; is for &lt;em&gt;link&lt;/em&gt; (a directed edge where a path is allowed to traverse).
Our graph has 4 segments and 2 paths through those segments, named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;.
(Also known as 🎷 and 🕌 in the picture above.)
There are also &lt;a href=&quot;https://jef.works/blog/2017/03/28/CIGAR-strings-for-dummies/&quot;&gt;CIGAR alignment strings&lt;/a&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0M&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;, but these don&amp;rsquo;t matter much for this post.&lt;/p&gt;

&lt;p&gt;The most interesting part is probably those comma-separated lists of steps in the path lines, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1+,2+,4-&lt;/code&gt; for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; path.
Each step has the name of a segment (declared in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; line) and a direction (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;).
All our segments&amp;rsquo; names here happen to be numbers, but the GFA text format doesn&amp;rsquo;t actually require that.&lt;/p&gt;

&lt;h2 id=&quot;a-slow-obvious-data-model&quot;&gt;A Slow, Obvious Data Model&lt;/h2&gt;

&lt;p&gt;How would you represent the fundamental data model that GFA files encode?
You can find &lt;a href=&quot;https://github.com/ggonnella/gfapy&quot;&gt;dozens&lt;/a&gt; &lt;a href=&quot;https://github.com/Tharos-ux/gfagraphs&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://github.com/AlgoLab/pygfa&quot;&gt;libraries&lt;/a&gt; &lt;a href=&quot;https://github.com/lh3/gfatools&quot;&gt;for&lt;/a&gt; &lt;a href=&quot;https://github.com/will-rowe/gfa&quot;&gt;parsing&lt;/a&gt; &lt;a href=&quot;https://github.com/edawson/gfakluge&quot;&gt;and&lt;/a&gt; &lt;a href=&quot;https://github.com/chfi/rs-gfa&quot;&gt;manipulating&lt;/a&gt; &lt;a href=&quot;https://github.com/ban-m/gfa_rust&quot;&gt;GFAs&lt;/a&gt; on GitHub or wherever, but those are all trying to be &lt;em&gt;useful:&lt;/em&gt;
they&amp;rsquo;re optimized to be fast, or specialized for a specific kind of analysis.
To understand what&amp;rsquo;s actually going on in GFA files, a genomically naive hacker like me needs something much less sophisticated: the most straightforward possible in-memory data model that can parse and pretty-print GFA text.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cs.cornell.edu/~amohan/&quot;&gt;Anshuman Mohan&lt;/a&gt; and I made a tiny Python library, &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/mygfa&quot;&gt;mygfa&lt;/a&gt;, that tries to play this explanatory role.
Here are pretty much all the &lt;a href=&quot;https://cucapra.github.io/pollen/mygfa/&quot;&gt;important data structures&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/~asampson/media/flatgfa/mygfa.svg&quot; class=&quot;img-responsive lightbg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Graph&lt;/code&gt; object holds Python lists and dictionaries to contain all the segments, paths, and links in a GFA file.
Maybe the only semi-interesting class here is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handle&lt;/code&gt;, which is nothing more than a pair of a segment&amp;mdash;referenced by name&amp;mdash;and an orientation (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt; in the GFA syntax).&lt;/p&gt;

&lt;p&gt;The mygfa data model is ridiculously inefficient.
That&amp;rsquo;s intentional!
We want to reflect exactly what&amp;rsquo;s going on in the GFA file in the most obvious, straightforward way possible.
That means there are strings and hash tables all over the place, including where it seems kind of silly to use them.
For example, because handles refer to segments by name, you have to look up the actual segment in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.segments&lt;/code&gt;.
Like, if you want to print out the DNA sequence for a link&amp;rsquo;s vertices, you&amp;rsquo;d do this:&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;segments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;segments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While mygfa is the wrong tool for the job for practical pangenomics computations,
we hope it&amp;rsquo;s useful for situations where clarity matters a lot and scale matters less:
learning about the domain,
exploratory design phases that come before high-performance implementations,
and &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/slow_odgi&quot;&gt;reference implementations&lt;/a&gt; for &amp;ldquo;real&amp;rdquo; pangenomics software.&lt;/p&gt;

&lt;h2 id=&quot;as-slow-as-possible&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/As_Slow_as_Possible&quot;&gt;As Slow as Possible&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cs.cornell.edu/~amohan/&quot;&gt;Anshuman&lt;/a&gt; took this &amp;ldquo;slow and steady&amp;rdquo; approach a step farther and used mygfa to implement a slew of pangenomic analyses.
We call the result &lt;a href=&quot;https://github.com/cucapra/pollen/tree/main/slow_odgi&quot;&gt;slow-odgi&lt;/a&gt;, because it&amp;rsquo;s a worse version of &lt;a href=&quot;https://odgi.readthedocs.io/en/latest/&quot;&gt;odgi&lt;/a&gt;, an actually good pangenomic toolkit that our collaborators wrote in C++.
Our slow-odgi tool currently implements 12 of odgi&amp;rsquo;s 44 commands.
We have an elaborate &lt;a href=&quot;https://github.com/cucapra/pollen/blob/main/tests/turnt.toml&quot;&gt;differential testing setup&lt;/a&gt; that checks, for a bunch of real input GFA files, that odgi and slow-odgi output byte-for-byte identical results.&lt;/p&gt;

&lt;figure style=&quot;max-width: 250px;&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/paths-mini-slow.svg&quot; alt=&quot;A bar chart comparing the speed of odgi and slow-odgi extracting the path names from 3 GFA files.&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;Slow-odgi is just way, way slower.
This plot compares the running time for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;odgi paths&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slow_odgi paths&lt;/code&gt; on 3 very small GFA files (295&amp;nbsp;kB, 1.0&amp;nbsp;MB, and 1.6&amp;nbsp;MB).
Odgi is sort of cheating here because it works on an optimized binary representation of the GFA file, and we&amp;rsquo;re not counting the time to do the conversion here, whereas slow-odgi has to parse that text format you saw at the top of this post.
For that and many other reasons, slow-odgi is more than 10&amp;times; slower, even for these little GFAs.&lt;/p&gt;

&lt;p&gt;In exchange, it&amp;rsquo;s also way shorter.
Here&amp;rsquo;s the code for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paths&lt;/code&gt; command that just extracts a list of the paths in a graph, &lt;a href=&quot;https://github.com/cucapra/pollen/blob/main/slow_odgi/slow_odgi/paths.py&quot;&gt;in slow-odgi&lt;/a&gt; and then &lt;a href=&quot;https://github.com/pangenome/odgi/blob/master/src/subcommand/paths_main.cpp&quot;&gt;in odgi&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;figrow&quot;&gt;
&lt;figure style=&quot;width: 55%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/code-slow-odgi.png&quot; alt=&quot;A big, chunky code listing of slow-odgi&apos;s Python implementation of path extraction.&quot; /&gt;
&lt;figcaption&gt;The entirety of &lt;a href=&quot;https://github.com/cucapra/pollen/blob/main/slow_odgi/slow_odgi/paths.py&quot;&gt;&lt;code&gt;paths.py&lt;/code&gt; from slow-odgi&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure style=&quot;max-width: 43%&quot;&gt;
&lt;img src=&quot;/~asampson/media/flatgfa/code-odgi.png&quot; alt=&quot;A ridiculously tiny grid of 9 pieces of odgi&apos;s C++ implementation of path extraction.&quot; /&gt;
&lt;figcaption&gt;Less than half of &lt;a href=&quot;https://github.com/pangenome/odgi/blob/master/src/subcommand/paths_main.cpp&quot;&gt;odgi&amp;rsquo;s &lt;code&gt;paths_main.cpp&lt;/code&gt;&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;This is a completely unfair comparison because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;odgi paths&lt;/code&gt; has many more features and is actually optimized; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slow_odgi paths&lt;/code&gt; does only one obvious thing, and it does it slowly.
But painfully obvious reference implementations were exactly what we needed to understand what better implementations could look like.&lt;/p&gt;

&lt;p&gt;I&amp;rsquo;ll write about a new, fast data representation for GFAs in a follow-up post.&lt;/p&gt;

</content>
    <summary type="html">&lt;p&gt;Here&amp;rsquo;s an overview of &lt;a href=&quot;https://github.com/GFA-spec/GFA-spec&quot;&gt;Graphical Fragment Assembly (GFA)&lt;/a&gt;, the standard text file format for representing &lt;a href=&quot;https://en.wikipedia.org/wiki/Pan-genome&quot;&gt;pangenomic&lt;/a&gt; variation graphs.
We wrote some Python libraries to represent and analzye GFA files in the most straightforward, readable way possible.&lt;/p&gt;

</summary>
  </entry>
  
</feed>
