summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/2026_REBASE/slides.css4
-rw-r--r--docs/2026_REBASE/slides.js2
-rw-r--r--print/src/main/java/com/seaofnodes/print/IRPrinter.java56
-rw-r--r--print/src/test/java/com/seaofnodes/print/PrinterTest.java45
4 files changed, 97 insertions, 10 deletions
diff --git a/docs/2026_REBASE/slides.css b/docs/2026_REBASE/slides.css
index 26438a39..46a4ca08 100644
--- a/docs/2026_REBASE/slides.css
+++ b/docs/2026_REBASE/slides.css
@@ -8,8 +8,8 @@
.kicker{display:flex;justify-content:space-between;align-items:center;font-size:17px;letter-spacing:.12em;text-transform:uppercase;font-weight:700;margin-bottom:30px}.kicker .tag{border:1px solid var(--accent);padding:7px 11px;border-radius:30px;color:var(--accent);letter-spacing:.05em;font-size:14px}
h1,h2,h3,p{margin:0}h1{font-size:100px;line-height:.99;letter-spacing:-.055em;font-weight:750}h2{font-size:62px;line-height:1.04;letter-spacing:-.04em;font-weight:700}h3{font-size:28px;letter-spacing:-.02em;line-height:1.14}p{font-size:25px;line-height:1.4}.muted{color:var(--muted)}.accent{color:var(--accent)}.orange{color:var(--orange)}.eyebrow{font-size:16px;font-weight:bold;text-transform:uppercase;letter-spacing:.13em;color:var(--muted);margin-bottom:14px}
.slide-content{flex:1;min-height:0}.footer{position:absolute;bottom:25px;left:72px;right:72px;border-top:1px solid var(--line);padding-top:13px;display:flex;justify-content:space-between;font-size:15px;color:var(--muted)}.footer strong{color:var(--ink)}.build-dots{display:flex;gap:7px}.build-dots i{width:7px;height:7px;border-radius:50%;background:#c9d1ca}.build-dots i.on{background:var(--accent)}
-.title-layout{display:grid;grid-template-columns:1.2fr .8fr;gap:82px;height:100%;align-items:center}.title-copy h1{font-size:116px;line-height:.98}.title-copy .subtitle{font-size:29px;line-height:1.35;max-width:650px;margin-top:30px}.speaker{margin-top:55px}.speaker strong{font-size:30px;display:block}.speaker span{font-size:19px;display:block;margin-top:9px;color:var(--muted)}.portrait{width:100%;height:440px;object-fit:cover;object-position:50% 35%;border-radius:3px}.photo-caption{font-size:14px;color:var(--muted);margin-top:12px}.title-line{width:74px;height:6px;background:var(--orange);margin-bottom:30px}
-.title-repo{position:absolute;left:72px;bottom:82px;font:23px Consolas,monospace}
+.title-layout{display:grid;grid-template-columns:1.2fr .8fr;gap:82px;height:100%;padding-bottom:110px;align-items:center}.title-copy h1{font-size:116px;line-height:.98}.title-copy .subtitle{font-size:29px;line-height:1.35;max-width:650px;margin-top:30px}.speaker{margin-top:55px}.speaker strong{font-size:30px;display:block}.speaker span{font-size:19px;display:block;margin-top:9px;color:var(--muted)}.portrait{width:100%;height:440px;object-fit:cover;object-position:50% 35%;border-radius:3px}.photo-caption{font-size:14px;color:var(--muted);margin-top:12px}.title-line{width:74px;height:6px;background:var(--orange);margin-bottom:30px}
+.title-layout .portrait-block{height:auto}.title-repo{position:absolute;left:72px;bottom:82px;font:26px/1.25 Consolas,monospace}.title-repo span{display:block;font:18px Arial,Helvetica,sans-serif;color:var(--ink);margin-bottom:8px}
.bio-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:82px;align-items:center;height:100%}.bio-name{margin-top:20px}.portrait-block{height:642px}.portrait-block .portrait{display:block}.bio-media .club-image{display:block;height:145px;margin-top:16px}.bio-media .club-caption{font-size:17px;margin:8px 0 0}.bio-list{list-style:none;padding:0;margin:35px 0 30px}.bio-list li{border-top:1px solid var(--line);padding:18px 0;font-size:25px;line-height:1.35}.bio-list small{display:block;font-size:20px;color:var(--muted);margin-top:6px}.club-image{width:100%;height:340px;object-fit:contain;mix-blend-mode:multiply}.club-caption{font-size:20px;margin:20px 0;text-align:center}.link-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}.link-row a{font-size:20px;padding:12px 16px;border:1px solid var(--line);border-radius:5px;text-decoration:none}.link-row a:hover{background:#e0e9e2}
.demo-layout{display:grid;grid-template-columns:1.03fr .97fr;gap:46px;margin-top:27px}.demo-code{background:#152e36;color:#ecf1e8;border-radius:9px;overflow:hidden;box-shadow:0 10px 25px #122b3410}.code-head{display:flex;justify-content:space-between;align-items:center;padding:13px 20px;background:#203d46;font:16px Consolas,monospace;color:#bdcec8}.code-head button{font-family:Arial,sans-serif;font-size:15px;background:#2e5356;color:#fff;border-color:#638582;padding:6px 13px}.demo-code pre{padding:24px 23px;margin:0;font:25px/1.5 Consolas,"Liberation Mono",monospace;tab-size:4;white-space:pre-wrap;overflow-wrap:normal}.demo-code.long pre{font-size:22px;line-height:1.45}.comment{color:#a6c0b5}.demo-actions{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}.demo-actions button,.demo-actions a{font-size:17px}.demo-actions a{display:inline-flex;align-items:center;padding:9px 2px}.launch{font:18px Consolas,monospace;display:block;margin-top:17px;color:var(--muted)}.demo-right{padding-top:5px}.thesis{font-size:28px;line-height:1.35;max-width:580px;margin-bottom:23px}
.fact{border-top:1px solid var(--line);padding:14px 0;display:flex;align-items:baseline;gap:18px}.fact b{font-size:18px;color:var(--accent);min-width:65px}.fact span{font-size:22px;line-height:1.32}.result{background:#e2ebe2;border-left:5px solid var(--accent);padding:19px 22px;margin-top:22px}.result strong{font:30px/1.3 Consolas,monospace}.result small{font-size:17px;display:block;margin-top:10px;color:var(--muted)}.watch{margin-top:20px;font-size:19px;line-height:1.45;color:var(--muted)}.watch strong{color:var(--ink)}.demo-tabs{display:flex;gap:8px;margin-top:17px}.demo-tabs button{font-size:16px}.demo-tabs button.active{background:var(--accent);color:white}
diff --git a/docs/2026_REBASE/slides.js b/docs/2026_REBASE/slides.js
index d27034d4..de888f13 100644
--- a/docs/2026_REBASE/slides.js
+++ b/docs/2026_REBASE/slides.js
@@ -76,7 +76,7 @@
R.slides=[
{
id:'title',label:'A Simple Tutorial',section:'REBASE 2026',minutes:1,steps:0,still:true,
- body:()=>'<div class="title-layout"><div class="title-copy"><div class="title-line"></div><h1>A <span class="accent">Simple</span><br>Tutorial.</h1><p class="subtitle">An open-source, all-Java compiler<br>modeled after Java’s C2.</p><div class="speaker"><strong>Cliff Click</strong><span>Sea of Nodes · from source to machine code</span></div></div><div class="portrait-block"><img class="portrait" src="assets/cliff-click-2020.jpeg" alt="Cliff Click seated on outdoor stone steps"><p class="photo-caption">REBASE / SPLASH · 2026</p></div></div>'+external(R.repo,'github.com/SeaOfNodes/Simple','title-repo'),
+ body:()=>'<div class="title-layout"><div class="title-copy"><div class="title-line"></div><h1>A <span class="accent">Simple</span><br>Tutorial.</h1><p class="subtitle">An open-source, all-Java compiler<br>modeled after Java’s C2.</p><div class="speaker"><strong>Cliff Click</strong><span>Sea of Nodes · from source to machine code</span></div></div><div class="portrait-block"><img class="portrait" src="assets/cliff-click-2020.jpeg" alt="Cliff Click seated on outdoor stone steps"><p class="photo-caption">REBASE / SPLASH · 2026</p></div></div>'+external(R.repo+'/blob/main/docs/2026_REBASE/index.html','<span>Follow along</span>github.com/SeaOfNodes/Simple/<br>docs/2026_REBASE/index.html','title-repo'),
notes:notes('An executable compiler tutorial',[
'Assume SSA and compiler background. Simple is a small language used to explain a real optimizing compiler architecture.',
'The compiler is written in Java, modeled on C2, and open source. The teaching unit is a complete compiler snapshot, with runnable tests and an explanation.',
diff --git a/print/src/main/java/com/seaofnodes/print/IRPrinter.java b/print/src/main/java/com/seaofnodes/print/IRPrinter.java
index bfe1cb05..b974ebe1 100644
--- a/print/src/main/java/com/seaofnodes/print/IRPrinter.java
+++ b/print/src/main/java/com/seaofnodes/print/IRPrinter.java
@@ -36,7 +36,7 @@ public final class IRPrinter<N extends BaseNode<N>> {
N p=_a.out(unit.start(),i);
if( _a.projection(p) ) ps.add(p);
}
- ps.sort(Comparator.comparingInt(_a::index));
+ ps.sort(projectionOrder());
for( N p : ps ) sb.append(line(p));
}
ArrayList<N> funs=new ArrayList<>(unit.functions());
@@ -107,6 +107,12 @@ public final class IRPrinter<N extends BaseNode<N>> {
N head=_a.input0(n);
if( head != null ) nodes.put(head,Boolean.TRUE);
}
+ // Complete projection groups without extending ordinary dependencies.
+ for( N n : new ArrayList<>(nodes.keySet()) )
+ for( int i=0; i<_a.nOuts(n); i++ ) {
+ N p=_a.out(n,i);
+ if( _a.projection(p) && _a.input0(p)==n ) nodes.put(p,Boolean.TRUE);
+ }
return render(nodes);
}
@@ -169,16 +175,21 @@ public final class IRPrinter<N extends BaseNode<N>> {
ArrayList<ArrayList<N>> body=new ArrayList<>();
for( int i=0; i<post.size(); i++ ) { blocks.put(post.get(i),i); body.add(new ArrayList<>()); }
if( body.isEmpty() ) body.add(new ArrayList<>());
+ for( N n : ns ) block(n,nodes,blocks);
+ IdentityHashMap<N,Integer> placed=new IdentityHashMap<>();
for( N n : ns )
- if( !_a.control(n) ) body.get(block(n,nodes,blocks)).add(n);
+ if( !_a.control(n) ) body.get(place(n,nodes,blocks,placed)).add(n);
StringBuilder sb=new StringBuilder();
IdentityHashMap<N,Boolean> emitted=new IdentityHashMap<>();
for( int i=0; i<body.size(); i++ ) {
if( i<post.size() ) {
N cfg=post.get(i);
- sb.append('\n');
+ if( !emitted.containsKey(cfg) ) sb.append('\n');
+ for( N n : body.get(i) )
+ if( !_a.phi(n) && !_a.projection(n) && _a.input0(n)==null ) emit(n,nodes,emitted,sb);
emitDefs(cfg,nodes,emitted,sb);
emitLine(cfg,emitted,sb);
+ projections(cfg,nodes,emitted,sb);
// A Phi's backedge belongs in the body, never ahead of the Phi.
for( N n : body.get(i) ) if( _a.phi(n) ) emitLine(n,emitted,sb);
}
@@ -235,6 +246,35 @@ public final class IRPrinter<N extends BaseNode<N>> {
StringBuilder sb) {
for( int i=0; i<_a.nIns(n); i++ ) emit(_a.in(n,i),nodes,emitted,sb);
}
+
+ // Floating data follows its uses. A loop Phi consumes its backedge value
+ // at the latch, not at the header of that (possibly enclosing) loop.
+ private int place(N n, IdentityHashMap<N,Boolean> nodes, IdentityHashMap<N,Integer> blocks,
+ IdentityHashMap<N,Integer> placed) {
+ Integer old=placed.get(n);
+ if( old!=null ) return old;
+ int early=blocks.get(n);
+ placed.put(n,early); // Close data cycles without changing compiler state.
+ if( _a.control(n) || _a.phi(n) || _a.projection(n) || _a.global(n) ||
+ _a.input0(n)!=null ) return early;
+ int late=Integer.MAX_VALUE;
+ for( int i=0; i<_a.nOuts(n); i++ ) {
+ N use=_a.out(n,i);
+ if( use==null || !nodes.containsKey(use) || _a.kind(use)==Kind.PARM ) continue;
+ if( _a.phi(use) && _a.input0(use)!=null && _a.kind(_a.input0(use))==Kind.LOOP ) {
+ N loop=_a.input0(use);
+ for( int j=1; j<_a.nIns(use); j++ )
+ if( _a.in(use,j)==n && j<_a.nIns(loop) ) {
+ N pred=_a.in(loop,j);
+ if( _a.projection(pred) ) pred=_a.input0(pred);
+ if( pred!=null && blocks.containsKey(pred) ) late=Math.min(late,blocks.get(pred));
+ }
+ } else late=Math.min(late,place(use,nodes,blocks,placed));
+ }
+ int b=late==Integer.MAX_VALUE ? early : Math.max(early,late);
+ placed.put(n,b);
+ return b;
+ }
private void emit(N n, IdentityHashMap<N,Boolean> nodes, IdentityHashMap<N,Boolean> emitted,
StringBuilder sb) {
if( n==null || emitted.containsKey(n) || !nodes.containsKey(n) || _a.control(n) || _a.phi(n) ) return;
@@ -256,12 +296,16 @@ public final class IRPrinter<N extends BaseNode<N>> {
ArrayList<N> ps=new ArrayList<>();
for( int i=0; i<_a.nOuts(n); i++ ) {
N p=_a.out(n,i);
- if( p!=null && nodes.containsKey(p) && _a.kind(p)==Kind.PROJ && _a.input0(p)==n ) ps.add(p);
+ if( _a.projection(p) && nodes.containsKey(p) && _a.input0(p)==n ) ps.add(p);
}
- ps.sort(Comparator.comparingInt(_a::index));
+ ps.sort(projectionOrder());
for( N p : ps ) emitLine(p,emitted,sb);
}
+ private Comparator<N> projectionOrder() {
+ return Comparator.comparingInt(_a::index).thenComparingInt(_a::id);
+ }
+
/** Scheduling already determines order; inspect it without sorting compiler arrays. */
public String scheduled(ArrayList<N> blocks) {
StringBuilder sb=new StringBuilder();
@@ -283,7 +327,7 @@ public final class IRPrinter<N extends BaseNode<N>> {
N p=_a.out(use,j);
if( p!=null && _a.kind(p)==Kind.PROJ && _a.input0(p)==use ) ps.add(p);
}
- ps.sort(Comparator.comparingInt(_a::index));
+ ps.sort(projectionOrder());
for( N p : ps ) emitLine(p,emitted,sb);
}
}
diff --git a/print/src/test/java/com/seaofnodes/print/PrinterTest.java b/print/src/test/java/com/seaofnodes/print/PrinterTest.java
index d63cc6cb..1cbe8a11 100644
--- a/print/src/test/java/com/seaofnodes/print/PrinterTest.java
+++ b/print/src/test/java/com/seaofnodes/print/PrinterTest.java
@@ -29,7 +29,7 @@ public class PrinterTest {
}
public static void main(String[] args) {
- expression(); loop(); functions(); assembly();
+ expression(); loop(); ordering(); functions(); assembly();
System.out.println("Shared printer contracts passed");
}
static void expression() {
@@ -90,6 +90,49 @@ public class PrinterTest {
}
static int row(String text,String name) { return text.indexOf(" "+name+" "); }
+ static void ordering() {
+ var adapter=new Adapter() {
+ @Override public int index(N n) { return switch(n.name) {
+ case "Minus" -> -1; case "Zero", "Again" -> 0; case "Two" -> 2;
+ default -> n._nid;
+ }; }
+ };
+ IRPrinter<N> printer=new IRPrinter<>(adapter);
+ N multi=new N(1,"Multi",Kind.MULTI);
+ N two=new N(2,"Two",Kind.PROJ,multi);
+ N minus=new N(3,"Minus",Kind.PROJ,multi);
+ N zero=new N(4,"Zero",Kind.CPROJ,multi);
+ N again=new N(5,"Again",Kind.PROJ,multi);
+ String text=printer.prettyPrint(two,0);
+ int[] ids=text.lines().filter(s -> !s.isBlank()).mapToInt(s -> Integer.parseInt(s.trim().split("\\s+")[0])).toArray();
+ assert Arrays.equals(ids,new int[]{1,3,4,5,2}) : text;
+ assert multi.outs.equals(Arrays.asList(two,minus,zero,again));
+
+ N outer=new N(10,"Outer",Kind.LOOP,null,null);
+ N inner=new N(11,"Inner",Kind.LOOP,null,outer);
+ N op=new N(12,"OPhi",Kind.PHI,outer,null);
+ N ip=new N(13,"IPhi",Kind.PHI,inner,null);
+ N iv=new N(14,"IValue",Kind.DATA,null,ip);
+ N ov=new N(15,"OValue",Kind.DATA,null,ip,op);
+ ip.add(iv); op.add(ov);
+ N branch=new N(16,"Branch",Kind.CTRL,inner,ip);
+ N left=new N(17,"Left",Kind.CPROJ,branch), right=new N(18,"Right",Kind.CPROJ,branch);
+ N join=new N(19,"Join",Kind.REGION,null,left,right);
+ N il=new N(20,"ILatch",Kind.CTRL,join,ip);
+ N ib=new N(21,"IBack",Kind.CPROJ,il), ie=new N(22,"IExit",Kind.CPROJ,il);
+ inner.add(ib);
+ N ol=new N(23,"OLatch",Kind.CTRL,ie,op);
+ N ob=new N(24,"OBack",Kind.CPROJ,ol), oe=new N(25,"OExit",Kind.CPROJ,ol);
+ outer.add(ob);
+ N after=new N(26,"After",Kind.CTRL,oe,op);
+ text=printer.prettyPrint(after,99);
+ N[] order={outer,op,inner,ip,branch,join,il,ov,ol,after};
+ for( int i=1; i<order.length; i++ )
+ assert row(text,order[i-1].name)<row(text,order[i].name) : text;
+ assert row(text,iv.name)<row(text,ov.name) : text;
+ assert text.equals(printer.prettyPrint(after,99));
+ }
+
static void functions() {
N start=new N(1,"Start",Kind.START), stop=new N(2,"Stop",Kind.STOP);
N one=new N(3,"One",Kind.CONSTANT,start);