Skip to content

Commit c335e74

Browse files
committed
Apply Matt's feedback (except for the royal we)
1 parent 60d271f commit c335e74

File tree

6 files changed

+63
-18
lines changed

6 files changed

+63
-18
lines changed

_posts/2025-10-15-iongraph-web.md renamed to _posts/2025-10-28-iongraph-web.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ author: Ben Visness
1111
width: calc(min(100vw, 1280px) - (45px * 2));
1212
}
1313

14+
.flex {
15+
display: flex;
16+
}
17+
18+
.g2 {
19+
gap: 0.5rem;
20+
}
21+
1422
.ba {
1523
border: 1px solid var(--color-primary);
1624
}
@@ -101,6 +109,10 @@ author: Ben Visness
101109
outline: none;
102110
}
103111

112+
#pass-slider {
113+
flex-grow: 1;
114+
}
115+
104116
.demoblock {
105117
width: 64px;
106118
height: 48px;
@@ -159,8 +171,12 @@ We recently overhauled our internal tools for visualizing the compilation of Jav
159171
<div id="graph-container"></div>
160172
<div id="legend">
161173
<span id="pass-name">&nbsp;</span>
162-
<input id="pass-slider" type="range" list="pass-slider-markers" value="0" disabled>
163-
<datalist id="pass-slider-markers"></datalist>
174+
<div class="flex g2">
175+
<button id="pass-prev" disabled>Prev</button>
176+
<input id="pass-slider" type="range" list="pass-slider-markers" value="0" disabled>
177+
<datalist id="pass-slider-markers"></datalist>
178+
<button id="pass-next" disabled>Next</button>
179+
</div>
164180
</div>
165181
</div>
166182
</div>
@@ -268,13 +284,13 @@ We will now go through the entire iongraph layout algorithm from start to finish
268284

269285
### Step 1: Layering
270286

271-
We first assign the basic blocks into horizontal tracks. This is very simple; we just start at layer 0 and recursively walk the graph, incrementing the layer number as we go. As we go, we track the "height" of each loop, not in pixels, but in layers.
287+
We first sort the basic blocks into horizontal tracks called "layers". This is very simple; we just start at layer 0 and recursively walk the graph, incrementing the layer number as we go. As we go, we track the "height" of each loop, not in pixels, but in layers.
272288

273-
We also take this opportunity to start positioning nodes "inside" and "outside" of loops. Whenever we see an edge that exits a loop, we defer the layering of the destination block until we are done layering the loop contents, at which point we know the loop's height.
289+
We also take this opportunity to vertically position nodes "inside" and "outside" of loops. Whenever we see an edge that exits a loop, we defer the layering of the destination block until we are done layering the loop contents, at which point we know the loop's height.
274290

275291
A note on implementation: nodes are visited multiple times throughout the process, not just once. This can produce a quadratic explosion for large graphs, but we find that an early-out is sufficient to avoid this problem in practice.
276292

277-
The animation below shows the layering algorithm in action. Notice how the final block in the graph is visited twice, once after each loop that branches to it, and in each case, the block is deferred until the entire loop has been layered. The final position of the block is below the entirety of both loops, rather than directly below one of its predecessors as Graphviz would do. (Remember, horizontal and vertical positions have not yet been computed; the positions of the blocks in this diagram are hardcoded for demonstration purposes.)
293+
The animation below shows the layering algorithm in action. Notice how the final block in the graph is visited twice, once after each loop that branches to it, and in each case, the block is deferred until the entire loop has been layered, rather than processed immediately after its predecessor block. The final position of the block is below the entirety of both loops, rather than directly below one of its predecessors as Graphviz would do. (Remember, horizontal and vertical positions have not yet been computed; the positions of the blocks in this diagram are hardcoded for demonstration purposes.)
278294

279295
<details>
280296
<summary>Implementation pseudocode</summary>
@@ -713,7 +729,7 @@ At the end of this step, all nodes have a fixed X-coordinate and will not be mod
713729
{ id: 1, layer: 2, lh: 1, succs: [2, 300] },
714730
{ id: 200, layer: 2, succs: [9], dummy: true, upward: true, dst: 9 },
715731
{ id: 300, layer: 3, succs: [400], dummy: true, upward: false, dst: 10 },
716-
{ id: 2, layer: 3, lh: 1, succs: [3, 4] },
732+
{ id: 2, layer: 3, lh: 1, succs: [3, 4] },
717733
{ id: 301, layer: 3, succs: [200], dummy: true, upward: true, dst: 9 },
718734
{ id: 400, layer: 4, succs: [500], dummy: true, upward: false, dst: 10 },
719735
{ id: 3, layer: 4, lh: 1, succs: [501, 5, 6, 7] },
@@ -1140,15 +1156,16 @@ The details of rendering are out of scope for this article, and depend on the sp
11401156

11411157
When rendering edges, we use a style inspired by [railroad diagrams](https://en.wikipedia.org/wiki/Syntax_diagram). These have many advantages over the Bézier curves employed by Graphviz. First, straight lines feel more organized and are easier to follow when scrolling up and down. Second, they are easy to route (vertical when crossing layers, horizontal between layers). Third, they are easy to coalesce when they share a destination, and the junctions provide a clear indication of the edge's direction. Fourth, they always cross at right angles, improving clarity and reducing the need to avoid edge crossings in the first place.
11421158

1143-
Consider the following example. There are several edge crossings that may be considered undesirable, yet the edges and their directions remain clear. Of particular note is the vertical junction on the left: not only is it immediately clear that these edges share a destination, but the junction itself signals that the edges are flowing downward. We find this much more pleasant than the "rat's nest" that Graphviz tends to produce.
1159+
Consider the following example. There are several edge crossings that may traditionally be considered undesirable—yet the edges and their directions remain clear. Of particular note is the vertical junction highlighted in red on the left: not only is it immediately clear that these edges share a destination, but the junction itself signals that the edges are flowing downward. We find this much more pleasant than the "rat's nest" that Graphviz tends to produce.
1160+
1161+
<img alt="Examples of railroad-diagram edges" src="/assets/img/iongraph-edge-examples-highlighted.png" width="716">
11441162

1145-
<img alt="Examples of railroad-diagram edges" src="/assets/img/iongraph-edge-examples.png" width="716">
11461163

11471164
## Why does this work?
11481165

11491166
It may seem surprising that such a simple (and stupid) layout algorithm could produce such readable graphs, when more sophisticated layout algorithms struggle. However, we feel that the algorithm succeeds _because_ of its simplicity.
11501167

1151-
Most graph layout algorithms are optimization problems, where error is minimized on some chosen metrics. However, these metrics seem to correlate poorly to readability in practice. For example, it seems good in theory to rearrange nodes to minimize edge crossings. But in practice, simple rules for edge routing seem to produce more readable results, and we achieve greater layout stability as a result. Similarly, layout rules like "align parents with their children" produce more readable results than "minimize the lengths of edges".
1168+
Most graph layout algorithms are optimization problems, where error is minimized on some chosen metrics. However, these metrics seem to correlate poorly to readability in practice. For example, it seems good in theory to rearrange nodes to minimize edge crossings. But a predictable order of nodes seems to produce more sensible results overall, and simple rules for edge routing are sufficient to keep things tidy. (As a bonus, this also gives us layout stability from pass to pass.) Similarly, layout rules like "align parents with their children" produce more readable results than "minimize the lengths of edges".
11521169

11531170
Furthermore, by rejecting the optimization problem, a human author gains more control over the layout. We are able to position nodes "inside" of loops, and push post-loop content down in the graph, _because_ we reject this global constraint-solver approach. Minimizing "error" is meaningless compared to a human _maximizing_ meaning through thoughtful design.
11541171

@@ -1166,13 +1183,13 @@ Perhaps programmers ought to put less trust into magic optimizing systems, espec
11661183

11671184
## Future work
11681185

1169-
We have already integrated iongraph into the Firefox profiler, making it easy for us to view the graphs of the most expensive or impactful functions we find in our performance work. Unfortunately, this is only available in specific builds of the SpiderMonkey shell, and is not available in full browser builds. This is due to architectural differences in how profiling data is captured and the flags with which the browser and shell are built. We would love for Firefox users to someday be able to view these graphs themselves, but at the moment we have no plans to expose this to the browser.
1186+
We have already integrated iongraph into the Firefox profiler, making it easy for us to view the graphs of the most expensive or impactful functions we find in our performance work. Unfortunately, this is only available in specific builds of the SpiderMonkey shell, and is not available in full browser builds. This is due to architectural differences in how profiling data is captured and the flags with which the browser and shell are built. We would love for Firefox users to someday be able to view these graphs themselves, but at the moment we have no plans to expose this to the browser. However, one bug tracking some related work can be found [here](https://bugzilla.mozilla.org/show_bug.cgi?id=1987005).
11701187

11711188
In the meantime, however, we plan to continue updating iongraph with more features to assist us in our work. We may in the future update the tool to add richer navigation, search features, and visualization of register allocation info. Ultimately, though, we are likely to work on iongraph sporadically as we need it, with little mind for a product roadmap.
11721189

11731190
To experiment with iongraph locally, you can run a debug build of the SpiderMonkey shell with `IONFLAGS=logs`; this will dump information to `/tmp/ion.json`. This file can then be loaded into the [standalone deployment of iongraph](https://mozilla-spidermonkey.github.io/iongraph/). Please be aware that the user experience is rough and unpolished in its current state.
11741191

1175-
The source code for iongraph can be found on [GitHub](https://github.com/mozilla-spidermonkey/iongraph).
1192+
The source code for iongraph can be found on [GitHub](https://github.com/mozilla-spidermonkey/iongraph). If this subject interests you, we would welcome contributions to both the browser and iongraph itself. The best place to reach us is our [Matrix chat](https://chat.mozilla.org/#/room/#spidermonkey:mozilla.org).
11761193

11771194
<script>
11781195
// Terrible code to put code blocks inside HTML tags, because our markdown

_sass/_layout.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ h1 {
3131
}
3232

3333
.hidden {
34-
display:none;
34+
display: none;
3535
}
3636

3737
@media (min-width: $bp-tablet) {
3838
.container {
39-
padding: 0 45px;
39+
padding: 0 45px 30px;
4040
}
41-
}
41+
}

_support/iongraph-article/src/main.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const containerEl = document.getElementById("graph-container");
1212
const passNameEl = document.getElementById("pass-name")
1313
const passSliderEl = document.getElementById("pass-slider");
1414
const passSliderMarkersEl = document.getElementById("pass-slider-markers");
15+
const passPrevEl = document.getElementById("pass-prev");
16+
const passNextEl = document.getElementById("pass-next");
1517

1618
export async function run(editorExtraStyles) {
1719
const editor = basicEditor(
@@ -71,7 +73,7 @@ for (let i = 0; i < 30; i++) {
7173
let graph;
7274
let passes = [];
7375
function updateGraph(pass) {
74-
passNameEl.innerText = pass.name;
76+
passNameEl.innerText = `After ${pass.name}`;
7577

7678
const previousState = graph?.exportState();
7779

@@ -88,14 +90,26 @@ for (let i = 0; i < 30; i++) {
8890
function setUIEnabled(enabled) {
8991
if (enabled) {
9092
passSliderEl.removeAttribute("disabled");
93+
passPrevEl.removeAttribute("disabled");
94+
passNextEl.removeAttribute("disabled");
9195
} else {
9296
passSliderEl.setAttribute("disabled", "disabled");
97+
passPrevEl.setAttribute("disabled", "disabled");
98+
passNextEl.setAttribute("disabled", "disabled");
9399
}
94100
}
95101

96102
passSliderEl.addEventListener("input", () => {
97103
updateGraph(passes[passSliderEl.value]);
98104
});
105+
passPrevEl.addEventListener("click", () => {
106+
passSliderEl.value = Math.max(0, Number(passSliderEl.value) - 1);
107+
updateGraph(passes[passSliderEl.value]);
108+
});
109+
passNextEl.addEventListener("click", () => {
110+
passSliderEl.value = Math.min(passes.length - 1, Number(passSliderEl.value) + 1);
111+
updateGraph(passes[passSliderEl.value]);
112+
});
99113

100114
while (true) {
101115
let resolvePendingRun;
32.3 KB
Loading

assets/js/iongraph/main.js

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/iongraph/main.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)