<p><a href="https://www.haskell.org/ghc/" rel="noreferrer">GHC</a> is an optimizing Haskell compiler. Since Haskell is lazy and mutation is rare, control flow and data flow are often very closely aligned. Therefore, it is often most useful to think about evaluation of Haskell programs in terms of <a href="https://en.wikipedia.org/wiki/Graph_reduction" rel="noreferrer">graph reduction</a>. GHC implements an abstract graph reduction machine known as the spineless, tagless G-machine, or <a href="https://dl.acm.org/doi/10.1145/99370.99385" rel="noreferrer">STG</a>.</p>
<p>One of the key insights of the <a href="https://doi.org/10.1017/S0956796800000319" rel="noreferrer">original work on STG</a> (which, incidentally, predates Haskell) is that the graphs reduced by this machine can be thought of as a small, functional language, where sharing in the graph structure is denoted using <code>let</code>. What’s more, this language can be used directly as the intermediate representation of an optimizing compiler. Since lazy evaluation is entirely demand-driven, an STG program is, in a very real sense, a dataflow graph used directly as IR.</p>
<p>Much more recently, <a href="https://dl.acm.org/doi/abs/10.1145/3391902" rel="noreferrer">RVSDG</a> (<a href="https://arxiv.org/pdf/1912.05036.pdf" rel="noreferrer">pdf</a>) was published, which sounds in some ways <em>remarkably</em> similar to STG:</p>
<blockquote>
<p>We present the Regionalized Value State Dependence Graph (RVSDG) IR for optimizing compilers. The RVSDG is a data flow centric IR where nodes represent computations, edges represent computational dependencies, and regions capture the hierarchical structure of programs. It represents programs in demand-dependence form, implicitly supports structured control flow, and models entire programs within a single IR.</p>
</blockquote>
<p>The authors choose to illustrate terms in their IR diagrammatically rather than as a small programming language, but it seems quite similar to STG:</p>
<ul>
<li><p>Like STG, terms in RVSDG are nested graphs that encode data dependence.</p>
</li>
<li><p>RVSDG’s <em>γ</em>-nodes encode multi-way conditional branching, which corresponds directly to STG’s <code>case</code> expressions.</p>
</li>
<li><p>RVSDG’s <em>θ</em>-nodes encode <code>do</code>-<code>while</code> loops, which correspond directly to tail-recursive functions in STG.</p>
</li>
<li><p>RVSDG’s <em>λ</em>-nodes encode functions, just as STG’s lambda expressions do.</p>
</li>
<li><p>RVSDG’s <em>δ</em>-nodes represent references to global variables, which in STG are represented directly as references to global variables.</p>
</li>
<li><p>RVSDG’s <em>φ</em>-nodes encode mutually-recursive environments, which correspond directly to STG’s <code>letrec</code> expressions.</p>
</li>
<li><p>RVSDG’s <em>ω</em>-nodes represent compilation units, which in STG are called modules.</p>
</li>
<li><p>RVSDG uses artificial “state edges” to encode ordering invariants in terms of data flow, and STG does exactly the same thing using special <code>State#</code> pseudo-values.</p>
</li>
</ul>
<p>The correspondence is, frankly, a little astonishing. Are these two models truly completely equivalent? Is RVSDG “merely” independent reinvention of STG? Or does something significant about them differ that I am overlooking?</p>
<p>Also, a little over five years ago, GHC’s implementation of STG was extended with <a href="https://dl.acm.org/doi/abs/10.1145/3062341.3062380" rel="noreferrer">join points</a>, which are like phi nodes in SSA. Do these correspond to anything in RVSDG?</p>

<optimization><haskell><intermediate-representation><data-flow-analysis>
What is the relationship between STG and RVSDG?
CC BY-SA 4.0
2024-04-12T23:18:25.830Z
Is one difference between STG and RVSDGs that STG has scopes and named bindings?
I'm new to STG, but it seems like in STG you can refer to variables outside of the scope of a let or letrec.
While in RVSDG everything has to be passed through explicitly, so there are no names of variables (and thus no alpha-equivalence problems)
Oliver Flatt
CC BY-SA 4.0
2024-05-22T14:18:30.843Z
@OliverFlatt I think that’s true, though one can see that difference largely as a matter of representation. Since all lexical closure in STG is tracked explicitly (that is, lambdas and thunks maintain an explicit list of the bindings they close over), and all variable names are unique, it would be entirely possible to represent STG as a graph without explicit named bindings. Indeed, the original STG paper notes that presenting STG as a small functional language rather than as a graph was an intentional presentational choice, but really just a matter of perspective.
Alexis King
CC BY-SA 4.0
2023-07-01T08:54:53.923Z
Quite possible that the authors are entirely oblivious to STG and the entire body of research on functional languages implementation, which is pretty evident from the list of citations in the paper. I'm not surprised, these two sub-cultures always had remarkably little overlap for some historical reasons. Every time I mention, say,  CPS-SSA equivalence to anyone from either side of this divide, they seem genuinely baffled as if it's the first time they hear about the other side existence.
SK-logic
CC BY-SA 4.0
2023-07-01T19:13:20.107Z
@SK-logic To be clear, I am *assuming* the authors are oblivious to STG—and I’m certainly not accusing them of anything! Independent reinvention happens all the time. I’m just curious whether there are meaningful differences between the designs that I’m overlooking.
Alexis King
CC BY-SA 4.0
2023-07-01T19:49:17.330Z
@AlexisKing it's the only possible explanation I can see - otherwise similarities are sufficient to at least warrant a citation, so the lack of citations is consistent with the assumption that the authors belong to the academic sub-culture that was not exposed to any of the FP-related work. It works the other way too - a lot of people of the FP tribe are unaware of some of the important and influential publications on the classical compiler theory side.
SK-logic
CC BY-SA 4.0
2023-07-05T04:36:03.340Z
I guess there are some differences. For example, STG's case expressions are the only way to peer inside a data constructor. STG's lambdas also have separate environment and arguments, which are slightly different from traditional CPS-like IRs...
Pseudonym
<h1>The RVSDG is fairly similar to the STG, but not identical by any means.</h1>
<p>Alright, so I've done a bit of research into the two, based off the original papers for both the STG and the RVSDG, and all your noted equivalences hold. <code>State#</code> and the RVSDG's state edges are indeed weirdly similar, seeing as they serve basically the same purpose despite being in such different environments.</p>
<p>Join points sort of correspond to gamma nodes in the RVSDG, but they're not really exactly SSA phis. (Weirdly enough, the RVSDG uses &quot;phi&quot; to represent something completely different. I'm not sure I like their naming conventions.)</p>
<h2>Some of the differences:</h2>
<h3>Differences because of laziness:</h3>
<ul>
<li>The STG has &quot;update&quot; nodes on every lambda, which dictate specifics of evaluation - whether a given closure needs to be updated or not on reduction. The RVSDG doesn't need this.</li>
<li>The STG features explicit forcing of terms via <code>case</code>, which is also not needed in the RVSDG.</li>
</ul>
<h3>Differences caused by general operational focus:</h3>
<ul>
<li><p>The STG is much more runtime-focused (while still working perfectly fine as an IR, to be clear), as opposed to the RVSDG, which is only focused on being a good IR.</p>
</li>
<li><p>As part of this, (and as noted), the STG features <code>let</code>, which is the only method of heap allocation for closures and similar. The RVSDG has no such structure, as it's not intended for runtime usage. (In fact, the RVSDG paper doesn't even contain the word &quot;heap&quot;)</p>
</li>
<li><p>The RVSDG has a more imperative-focused design, obviously, and the optimisations that are &quot;evident&quot; by its structure follow as such. While things like theta-nodes (do-while) do correspond to tail recursion, they exist on their own in a way that would require more effort in something like the STG. Likewise for the STG and its purely-functional-focused design.</p>
</li>
<li><p>The RVSDG's general structure is more complex than the STG (though not by much), as it needs to permit a larger number of imperative structures.</p>
<ul>
<li>The RVSDG doesn't actually provide a &quot;grammar&quot; for their language, so it's slightly hard to provide an objective comparison, but it has ~6 main &quot;essential&quot; constructs (gamma, theta, lambda, delta, phi, omega), and the way they interact is somewhat complex. (Perhaps this is also slight observation bias, as the diagrammatic presentation does make it seem more complex).</li>
<li>In opposition, the STG has somewhere between 3 and 5 constructs (case, letrec, lambda, then arguably fail &amp; default), and the interactions between these are more simple.</li>
<li>Defining &quot;essential&quot; here to mean &quot;things that are described as the &quot;core&quot; of the given language.</li>
<li>I've omitted things like function application, datastructures, etc from this comparison because both have them, and they're close enough to the same basic concept as to be boring.</li>
</ul>
</li>
<li><p>The STG emerges basically &quot;naturally&quot; from GHC's Core language, whereas the RVSDG requires some effort put in to translate some languages to it. The paper notes unstructured control flow as being untranslatable.</p>
</li>
</ul>
<p>As part of the above:</p>
<p>I think that there is that there is a distinct language that emerges &quot;naturally&quot; from the STG (in the sense any real attempt interpreting this language would give you a version of at least a graph machine, the ST portion of the STG really just being optimizations), but there's no real distinct language that emerges from the RVSDG. Any attempt to interpret such a language could really just look like any other small imperative language, and could be implemented as such.</p>
<p>Whether one sees this as distinctness is up to personal taste, but I see it as such.</p>
<hr />
<p>(please feel free to edit if anyone notices more differences or similarities that might be useful)</p>

CC BY-SA 4.0
2024-05-22T00:05:28.140Z
2024-05-22T22:31:31.103Z
2024-05-22T22:31:31.103Z
blueberry
CC BY-SA 4.0
2024-05-22T14:11:57.957Z
This is a nice answer—thank you! I’m a little curious what you mean by “RVSDG has a much more imperative-focused design”. I agree that RVSDG was obviously designed in the context of imperative languages, but I’m not sure what aspects of its design you feel reflect that context, relative to STG. Similarly, I’m curious what you mean when you say that “RVSDG’s general structure is more complex than STG”—in what ways do you mean?
Alexis King
CC BY-SA 4.0
2024-05-22T21:46:50.283Z
@AlexisKing Thanks! For the former, the clearest signallers to me are the absence of any higher order representational functionality (you could of course perform a lowering before lowering to the RVSDG, but still), and general node design (giving "obvious" representation to structures commonly seen in imperative languages, like do-while loops and linear structured control flow like switch). To be honest though, "much" was probably overselling it a bit, and I'll change it. As for your second point, I'll edit the answer to quantify that a bit more.
blueberry
CC BY-SA 4.0
2024-05-22T22:05:28.773Z
Now that I look again, you probably could represent higher order functionality in the RVSDG? I take the fact that they don't seem to consider this possibility at all as further evidence that it's not really intended for functional language, though.
blueberry
# What is the relationship between STG and RVSDG?
Posted by: username_0 on 2023-07-01
[GHC](https://www.haskell.org/ghc/) is an optimizing Haskell compiler. Since Haskell is lazy and mutation is rare, control flow and data flow are often very closely aligned. Therefore, it is often most useful to think about evaluation of Haskell programs in terms of [graph reduction](https://en.wikipedia.org/wiki/Graph_reduction). GHC implements an abstract graph reduction machine known as the spineless, tagless G-machine, or [STG](https://dl.acm.org/doi/10.1145/99370.99385).

One of the key insights of the [original work on STG](https://doi.org/10.1017/S0956796800000319) (which, incidentally, predates Haskell) is that the graphs reduced by this machine can be thought of as a small, functional language, where sharing in the graph structure is denoted using `let`. What’s more, this language can be used directly as the intermediate representation of an optimizing compiler. Since lazy evaluation is entirely demand-driven, an STG program is, in a very real sense, a dataflow graph used directly as IR.

Much more recently, [RVSDG](https://dl.acm.org/doi/abs/10.1145/3391902) ([pdf](https://arxiv.org/pdf/1912.05036.pdf)) was published, which sounds in some ways *remarkably* similar to STG:

> We present the Regionalized Value State Dependence Graph (RVSDG) IR for optimizing compilers. The RVSDG is a data flow centric IR where nodes represent computations, edges represent computational dependencies, and regions capture the hierarchical structure of programs. It represents programs in demand-dependence form, implicitly supports structured control flow, and models entire programs within a single IR.

The authors choose to illustrate terms in their IR diagrammatically rather than as a small programming language, but it seems quite similar to STG:

* Like STG, terms in RVSDG are nested graphs that encode data dependence.
* RVSDG’s *γ*-nodes encode multi-way conditional branching, which corresponds directly to STG’s `case` expressions.
* RVSDG’s *θ*-nodes encode `do`-`while` loops, which correspond directly to tail-recursive functions in STG.
* RVSDG’s *λ*-nodes encode functions, just as STG’s lambda expressions do.
* RVSDG’s *δ*-nodes represent references to global variables, which in STG are represented directly as references to global variables.
* RVSDG’s *φ*-nodes encode mutually-recursive environments, which correspond directly to STG’s `letrec` expressions.
* RVSDG’s *ω*-nodes represent compilation units, which in STG are called modules.
* RVSDG uses artificial “state edges” to encode ordering invariants in terms of data flow, and STG does exactly the same thing using special `State#` pseudo-values.

The correspondence is, frankly, a little astonishing. Are these two models truly completely equivalent? Is RVSDG “merely” independent reinvention of STG? Or does something significant about them differ that I am overlooking?

Also, a little over five years ago, GHC’s implementation of STG was extended with [join points](https://dl.acm.org/doi/abs/10.1145/3062341.3062380), which are like phi nodes in SSA. Do these correspond to anything in RVSDG?
**Tags:** optimization haskell intermediate-representation data-flow-analysis
> **Comments:**
> **username_2**: Quite possible that the authors are entirely oblivious to STG and the entire body of research on functional languages implementation, which is pretty evident from the list of citations in the paper. I'm not surprised, these two sub-cultures always had remarkably little overlap for some historical reasons. Every time I mention, say,  CPS-SSA equivalence to anyone from either side of this divide, they seem genuinely baffled as if it's the first time they hear about the other side existence.  
> **username_0**: @SK-logic To be clear, I am *assuming* the authors are oblivious to STG—and I’m certainly not accusing them of anything! Independent reinvention happens all the time. I’m just curious whether there are meaningful differences between the designs that I’m overlooking.  
> **username_2**: @AlexisKing it's the only possible explanation I can see - otherwise similarities are sufficient to at least warrant a citation, so the lack of citations is consistent with the assumption that the authors belong to the academic sub-culture that was not exposed to any of the FP-related work. It works the other way too - a lot of people of the FP tribe are unaware of some of the important and influential publications on the classical compiler theory side.  
> **username_3**: I guess there are some differences. For example, STG's case expressions are the only way to peer inside a data constructor. STG's lambdas also have separate environment and arguments, which are slightly different from traditional CPS-like IRs...  
> **username_1**: Is one difference between STG and RVSDGs that STG has scopes and named bindings?
I'm new to STG, but it seems like in STG you can refer to variables outside of the scope of a let or letrec.
While in RVSDG everything has to be passed through explicitly, so there are no names of variables (and thus no alpha-equivalence problems)  
> **username_0**: @OliverFlatt I think that’s true, though one can see that difference largely as a matter of representation. Since all lexical closure in STG is tracked explicitly (that is, lambdas and thunks maintain an explicit list of the bindings they close over), and all variable names are unique, it would be entirely possible to represent STG as a graph without explicit named bindings. Indeed, the original STG paper notes that presenting STG as a small functional language rather than as a graph was an intentional presentational choice, but really just a matter of perspective.  
---
## 1 answers
### Answer by username_4 on 2024-05-22. Score: 2
# The RVSDG is fairly similar to the STG, but not identical by any means.

Alright, so I've done a bit of research into the two, based off the original papers for both the STG and the RVSDG, and all your noted equivalences hold. `State#` and the RVSDG's state edges are indeed weirdly similar, seeing as they serve basically the same purpose despite being in such different environments.

Join points sort of correspond to gamma nodes in the RVSDG, but they're not really exactly SSA phis. (Weirdly enough, the RVSDG uses "phi" to represent something completely different. I'm not sure I like their naming conventions.)

## Some of the differences:

### Differences because of laziness:

* The STG has "update" nodes on every lambda, which dictate specifics of evaluation - whether a given closure needs to be updated or not on reduction. The RVSDG doesn't need this.
* The STG features explicit forcing of terms via `case`, which is also not needed in the RVSDG.

### Differences caused by general operational focus:

* The STG is much more runtime-focused (while still working perfectly fine as an IR, to be clear), as opposed to the RVSDG, which is only focused on being a good IR.
* As part of this, (and as noted), the STG features `let`, which is the only method of heap allocation for closures and similar. The RVSDG has no such structure, as it's not intended for runtime usage. (In fact, the RVSDG paper doesn't even contain the word "heap")
* The RVSDG has a more imperative-focused design, obviously, and the optimisations that are "evident" by its structure follow as such. While things like theta-nodes (do-while) do correspond to tail recursion, they exist on their own in a way that would require more effort in something like the STG. Likewise for the STG and its purely-functional-focused design.
* The RVSDG's general structure is more complex than the STG (though not by much), as it needs to permit a larger number of imperative structures.

  + The RVSDG doesn't actually provide a "grammar" for their language, so it's slightly hard to provide an objective comparison, but it has ~6 main "essential" constructs (gamma, theta, lambda, delta, phi, omega), and the way they interact is somewhat complex. (Perhaps this is also slight observation bias, as the diagrammatic presentation does make it seem more complex).
  + In opposition, the STG has somewhere between 3 and 5 constructs (case, letrec, lambda, then arguably fail & default), and the interactions between these are more simple.
  + Defining "essential" here to mean "things that are described as the "core" of the given language.
  + I've omitted things like function application, datastructures, etc from this comparison because both have them, and they're close enough to the same basic concept as to be boring.
* The STG emerges basically "naturally" from GHC's Core language, whereas the RVSDG requires some effort put in to translate some languages to it. The paper notes unstructured control flow as being untranslatable.

As part of the above:

I think that there is that there is a distinct language that emerges "naturally" from the STG (in the sense any real attempt interpreting this language would give you a version of at least a graph machine, the ST portion of the STG really just being optimizations), but there's no real distinct language that emerges from the RVSDG. Any attempt to interpret such a language could really just look like any other small imperative language, and could be implemented as such.

Whether one sees this as distinctness is up to personal taste, but I see it as such.

---

(please feel free to edit if anyone notices more differences or similarities that might be useful)
> **Comments:**
> **username_0** (2024-05-22): This is a nice answer—thank you! I’m a little curious what you mean by “RVSDG has a much more imperative-focused design”. I agree that RVSDG was obviously designed in the context of imperative languages, but I’m not sure what aspects of its design you feel reflect that context, relative to STG. Similarly, I’m curious what you mean when you say that “RVSDG’s general structure is more complex than STG”—in what ways do you mean?  
> **username_4** (2024-05-22): @AlexisKing Thanks! For the former, the clearest signallers to me are the absence of any higher order representational functionality (you could of course perform a lowering before lowering to the RVSDG, but still), and general node design (giving "obvious" representation to structures commonly seen in imperative languages, like do-while loops and linear structured control flow like switch). To be honest though, "much" was probably overselling it a bit, and I'll change it. As for your second point, I'll edit the answer to quantify that a bit more.  
> **username_4** (2024-05-22): Now that I look again, you probably could represent higher order functionality in the RVSDG? I take the fact that they don't seem to consider this possibility at all as further evidence that it's not really intended for functional language, though.  
---
