Open-source project
SeaOfNodes/Simple avatar
SeaOfNodes/Simple

Sea of Nodes Simple teaches a compiler IR one chapter at a time

A Simple showcase for the Sea-of-Nodes compiler IR

923 stars60 forksJavaApache-2.0

At a glance

What is it?
The Simple repository is a teaching implementation of the Sea-of-Nodes compiler IR, the graph-based intermediate representation behind HotSpot's C2, Google's V8 and Graal. It is built as a series of numbered chapters that each add one topic, from constant folding in Chapter 2 to graph colouring register allocation in Chapter 20.
Who is it for?
Simple suits a compiler engineer who wants to read a working Sea-of-Nodes implementation in the order the ideas build, and the chapter structure means you can stop at Chapter 9 and still have a coherent subset. It does not suit anyone looking for a production language or a reusable compiler library, because the README states the repo is not intended to be a complete language and the backend is explicitly secondary.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A showcase for the IR, with the language deliberately kept small

The repository states its purpose without hedging: it is intended to demonstrate the Sea-of-Nodes compiler IR. The Sea-of-Nodes is the core IR inside HotSpot's C2 compiler, Google's V8 compiler and Sun/Oracle's Graal compiler, and it is used for machine code generation in those industrial strength systems. Because the point is the IR rather than the language, Simple itself is a throwaway. It is strongly typed and object-oriented, with first-class functions rather than closures, and object references are pointers, with null pointer exceptions disallowed by the typing system. The README notes that arrays will probably be range-checked at some point, making Simple a fully safe language, and that Simple has a minimal syntax that can be parsed with a recursive descent parser. The primary language of the repository is Java.

Each chapter is a self-sufficient subset of the language

The teaching structure is the actual product here. The README describes a rough plan, subject to change, in which each chapter fully implements a subset of the Simple language and includes everything created in the previous chapter, and each also carries a detailed commentary on relevant aspects of the Sea-of-Nodes representation. That last point matters more than the language features do, since the commentary is where the IR concepts are explained against code you can run. The plan is a rough one and the chapter boundaries are still moving, as a separate document about an approved split shows, using descriptive chapter names while the teaching boundaries settle, with the numbered chapters remaining comparison baselines during the extraction. Two named snapshots exist already, one for SCCP within a function and one for SCCP across functions.

From constant folding to control flow in the first six chapters

The early chapters are where the graph representation becomes visible. Chapter 1 is a script that returns an integer literal, an empty function taking no arguments and returning a single integer value, which introduces the return statement. Chapter 2 adds binary arithmetic such as addition, subtraction, multiplication and division with constants, along with peephole optimization and simple constant folding. Chapter 3 introduces local variables and assignment statements, reading on the right hand side, SSA, and more peephole optimization when the local is a constant. Chapter 4 brings in a non-constant external variable input named arg, with binary and comparison operators over constants and arg, where non-zero values are truthy, plus peephole optimizations involving algebraic simplifications. Chapter 5 adds the if statement and CFG construction, and Chapter 6 covers peephole optimization around dead control flow. Six chapters in, you have arithmetic, SSA and a control flow graph.

Loops, phis and value numbering in chapters 7 to 9

The loop chapters are where a graph IR gets interesting and where the pedagogy matters most. Chapter 7 covers the while statement and looping constructs using an eager phi approach, and Chapter 8 continues with looping constructs, lazy phi creation, and the break and continue statements. The move from eager to lazy phi is the point of the pair, and having both in consecutive chapters is the kind of comparison a textbook would normally skip. Chapter 9 then introduces Global Value Numbering, iterative peepholes run to a fixpoint, and worklists. That chapter is where the earlier optimizations start paying off, because value numbering is what lets a graph merge nodes that are provably the same, and a worklist is what lets the peephole rules iterate until nothing changes.

Memory, aliases and scheduling from chapter 10 to chapter 13

Chapter 10 is where the language stops being purely scalar. It introduces user defined structs, pointers and null analysis, one memory value in SSA, loads and stores, and an executable evaluator. That evaluator matters later, since the README notes the backend starts with a Java evaluator, first introduced in Chapter 8, that directly interprets the SoN IR, and that code generation first appears in Chapter 19. Chapter 11 handles equivalence class aliasing with lazy memory partitioning using MemMerge, MemPhi and BulkMemPhi. Chapter 12 adds reference fields, forward references and recursive structs. Chapter 13 is Global Code Motion, which is scheduling. The README notes that Chapters 10 to 14 now group whole memory, lazy alias splitting, references, GCM and numeric types in that order, and that the memory representation is forwarded through Chapter 25, so a reader working through the current ordering sees one memory story rather than a refactored one.

Types, arrays, functions and the machine backend

The second half moves toward code generation. Chapter 14 covers numeric types, floats, narrow integers, ranges and rounding to f32. Chapter 15 introduces a one dimensional static length array type with array loads and stores. Chapter 16 adds constructors, and Chapter 17 splits into two: 17a covers binding mutability, reference permissions and deep read-only views, while 17b is syntax sugar including var, val, x+=y and for(init; test; next) body. Chapter 18 introduces functions and calls. Chapter 19 is instruction selection and portable compilation, Chapter 20 is graph colouring register allocation, Chapter 21 is instruction encodings and ELF, Chapter 22 is a Simple Hello, World, Chapter 23 revisits methods and types, Chapter 24 is chained conditionals and SCCP, and Chapter 25 covers modules, separate compilation and SSA construction with incomplete types. The repository targets x86-64, RISC-V and ARM64 with ahead-of-time compilation, with an eye towards JIT compilation.

Shared modules, a viewer and a printer you can inspect the graph with

The repository is not just chapter directories. An optional interactive graph viewer is shared in graph/, and Chapters 1 to 25 launch it with make view from the chapter directory. The debug printers are shared in print/, where Chapter 2 needs only a one-line expression format, and a shared BaseNode supplies identity and edge access to both the printers and the viewer, with chapter adapters adding semantic details as the IR grows. The instruction encoders in isa/ share x86-64, ARM and RISC-V byte emission for Chapters 21 to 25, along with ARM and RISC-V test evaluators. The split of responsibility there is deliberate: machine node selection, register allocation and code layout remain in each chapter, while the shared layer takes concrete operands and writes bytes. That way a chapter diff shows the algorithm rather than a rewrite of the encoder.

Building the whole set, or three chapters at a time

The top-level Makefile runs each chapter's tests, tags, release or lib target, and the README tells you to start with make lib on a fresh checkout and then make tests. To build and test just the memory chapters, you override the chapter list:

sh
make lib tests release CHAPTERS="chapter10 chapter11 chapter12"

The Makefile derives the chapter list from the wildcard of chapter directories that contain a pom.xml, appending the named chapters, and the actions are tests, tags, release and lib. The Make targets also build Chapter 25's native runtime and sys library before running the tests that use them, and Maven's Java build alone does not produce those prerequisites. Continuous integration runs make lib tests CTAGS= on Linux with JDK 21. The root Maven reactor includes every chapter, and Chapters 10 to 14 have portable IDEA module descriptors depending on the shared graph and print modules. Licence is Apache-2.0, and the last push was on 2026-10-19.

Editorial conclusion

Simple suits a compiler engineer who wants to read a working Sea-of-Nodes implementation in the order the ideas build, and the chapter structure means you can stop at Chapter 9 and still have a coherent subset. It does not suit anyone looking for a production language or a reusable compiler library, because the README states the repo is not intended to be a complete language and the backend is explicitly secondary. Before you start, run make lib on a fresh checkout and then make tests, and note that CI uses JDK 21 on Linux, since Maven alone does not build the native runtime and sys library that the later chapter tests need.

Frequently asked questions

What is the Sea-of-Nodes intermediate representation?

It is the core IR inside HotSpot's C2 compiler, Google's V8 compiler and Sun/Oracle's Graal compiler, and it is used for machine code generation in those systems. The Simple repository demonstrates it in Java.

How do I build and test the Simple compiler chapters?

Start with make lib on a fresh checkout and then make tests. To work on a subset, pass the chapter directories, for example make lib tests release CHAPTERS="chapter10 chapter11 chapter12".

Which chapters cover memory and aliasing?

Chapter 10 introduces structs, pointers, null analysis and one memory value in SSA. Chapter 11 covers equivalence class aliasing with lazy memory partitioning using MemMerge, MemPhi and BulkMemPhi, and Chapter 12 adds reference fields and recursive structs.

Does Simple generate real machine code?

The backend is described as both difficult and less important for the demonstration. Code generation first appears in Chapter 19, with instruction encodings and ELF in Chapter 21, and the repository targets x86-64, RISC-V and ARM64.

What licence is the Simple repository under?

The licence field on the repository says Apache-2.0, and the LICENSE file sits at the top level of the tree.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. SeaOfNodes/Simple on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/seaofnodes-simple.svg)](https://hysenlabs.com/projects/seaofnodes-simple)

Was this useful?