summaryrefslogtreecommitdiff
path: root/chapter29/src/main/java/com/seaofnodes/simple/print
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
committerCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
commitf4bd723d929e53475f7990374dca7b7dfcbbdc42 (patch)
tree7bb080119e7b88ad4ac9b30af019fe6a5e4ba9dd /chapter29/src/main/java/com/seaofnodes/simple/print
parent0ac0c9e10287972ac3af5a4ee43d26f0d5b318fe (diff)
downloadSimple-main.tar.gz
Simple-main.zip
Add Chapter 29 for tech debt cleanup with the parser splitmain
Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks.
Diffstat (limited to 'chapter29/src/main/java/com/seaofnodes/simple/print')
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java101
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java60
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java80
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java35
4 files changed, 276 insertions, 0 deletions
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
new file mode 100644
index 00000000..e8218afd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.print.AssemblyAdapter;
+import com.seaofnodes.print.ASMPrinter.Data;
+import com.seaofnodes.print.ASMPrinter.Pool;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.ArrayList;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+
+/** Machine-specific facts; traversal, columns and byte display live in print/. */
+public final class ASMPrinter extends AssemblyAdapter<Node> {
+ private final CodeGen _code;
+ private final Encoding _enc;
+ private ASMPrinter(CodeGen code) {
+ super(new IRPrinter());
+ _code=code;
+ _enc=code._encoding;
+ }
+ public static SB print(SB sb, CodeGen code) {
+ return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print());
+ }
+ @Override public ArrayList<Node> blocks() {
+ var blocks=new ArrayList<Node>();
+ var cfg=_enc==null ? _code._cfg : _enc._cfg;
+ if( cfg!=null ) for( Node n : cfg ) blocks.add(n);
+ return blocks;
+ }
+ @Override public String op(Node n) { return n instanceof MachNode m ? m.op() : n.label(); }
+ @Override public String register(Node n) { return _code.reg(n); }
+ @Override public String comment(Node n) { return n.comment(); }
+ @Override public String phiName(Node n) { return ((PhiNode)n)._label; }
+ @Override public String operands(Node n) {
+ SB sb=new SB();
+ if( n instanceof MachNode m ) m.asm(_code,sb);
+ else if( !(n._type instanceof TypeMem) ) {
+ sb.p(n._nid).p(": ");
+ for( Node def : n._inputs ) sb.p(def==null ? "___" : Integer.toString(def._nid)).p(' ');
+ }
+ return sb.toString();
+ }
+ @Override public boolean labelBlock(Node n) {
+ return !(n instanceof FunNode) && !(n instanceof IfNode) && !(n instanceof CallNode) &&
+ !(n instanceof CallEndNode) && !(n instanceof CProjNode && ir.input0(n) instanceof CallEndNode);
+ }
+ @Override public boolean hidden(Node n) {
+ if( n instanceof PhiNode || n instanceof ProjNode )
+ if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true;
+ if( postAlloc() && n instanceof CalleeSaveNode ) return true;
+ if( n instanceof MemMergeNode ) return true;
+ if( n instanceof EscapeNode ) return true;
+ return n.getClass()==ConstantNode.class;
+ }
+ @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() ||
+ (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); }
+ @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); }
+ @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); }
+ @Override public boolean littleEndian() { return _code._asmLittle; }
+ @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; }
+ @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; }
+ @Override public int offset(Node n) { return encoded() ? _enc.opStart(n) : -1; }
+ @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; }
+ private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) {
+ var data=new ArrayList<Data>();
+ for( Encoding.Relo r : relos ) {
+ if( r.readOnly()!=ro ) continue;
+ int align=1<<r._align;
+ int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align;
+ data.add(new Data(r._t,r._t.str(),r._align,size));
+ }
+ return data;
+ }
+ @Override public ArrayList<Pool> pools(int codeEnd) {
+ var pools=new ArrayList<Pool>();
+ if( !encoded() ) return pools;
+ int base=(codeEnd+15)&-16;
+ if( _enc._cpool.size()>0 )
+ pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true)));
+ base=(base+_enc._cpool.size()+15)&-16;
+ if( _enc._sdata.size()>0 )
+ pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false)));
+ return pools;
+ }
+ private ArrayList<Data> objects(boolean ro) {
+ var data=new ArrayList<Data>();
+ for( var obj : _enc._data.entries() )
+ if( !obj.external && obj.readOnly==ro )
+ data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size));
+ return data;
+ }
+ // Retain this diagnostic entry point for checking that pool printing never lays out types.
+ static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos,
+ boolean ro, String name) {
+ sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro))));
+ return address+bits.size();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
new file mode 100644
index 00000000..531a549a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.print.IRAdapter;
+import com.seaofnodes.simple.node.*;
+import java.util.ArrayList;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.CodeGen;
+
+/** This chapter's read-only IR facts and the familiar debugger entry points. */
+public final class IRPrinter extends IRAdapter<Node> {
+ private static final com.seaofnodes.print.IRPrinter<Node> PRINT =
+ new com.seaofnodes.print.IRPrinter<>(new IRPrinter());
+ @Override public boolean global(Node n) { return n.isConst(); }
+ @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); }
+ @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; }
+ @Override public Kind kind(Node n) {
+ if( n instanceof StartCUNode ) return Kind.UNIT;
+ if( n instanceof StopCUNode ) return Kind.UNIT;
+ if( n instanceof StartNode ) return Kind.START;
+ if( n instanceof StopNode ) return Kind.STOP;
+ if( n instanceof FunNode ) return Kind.FUN;
+ if( n instanceof LoopNode ) return Kind.LOOP;
+ if( n instanceof RegionNode ) return Kind.REGION;
+ if( n instanceof ParmNode ) return Kind.PARM;
+ if( n instanceof PhiNode ) return Kind.PHI;
+ if( n instanceof CallEndNode ) return Kind.CALL_END;
+ if( n instanceof CallNode ) return Kind.CALL;
+ if( n instanceof ReturnNode ) return Kind.RETURN;
+ if( n instanceof ConstantNode ) return Kind.CONSTANT;
+ if( n instanceof CProjNode ) return Kind.CPROJ;
+ if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ;
+ if( n instanceof CFGNode ) return Kind.CTRL;
+ if( n instanceof MultiNode ) return Kind.MULTI;
+ return Kind.DATA;
+ }
+ @Override public int index(Node n) {
+ if( n instanceof ProjNode p ) return p._idx;
+ if( n instanceof CProjNode p ) return p._idx;
+ return n._nid;
+ }
+ @Override public String functionName(Node n) { return ((FunNode)n)._name==null ? "" : ((FunNode)n)._name; }
+ @Override public String signature(Node n) {
+ FunNode fun=(FunNode)n;
+ return (fun._name==null ? "" : fun._name)+" "+fun.sig().str();
+ }
+
+ public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); }
+ public static SB printLine(Node n, SB sb) { return sb.p(PRINT.line(n)); }
+
+ public static String prettyPrint(CodeGen code) {
+ var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>();
+ for( var cu : code._compunits.values() ) {
+ var funs=new ArrayList<Node>();
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun._compunit==cu ) funs.add(fun);
+ units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs));
+ }
+ return PRINT.program(code._start,code._stop,units);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
new file mode 100644
index 00000000..9ca0ec51
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.graph.GraphAdapter;
+import com.seaofnodes.graph.GraphSnapshot.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */
+public class SimpleGraphAdapter extends GraphAdapter<Node> {
+
+ @Override protected boolean show(Node n) {
+ if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true;
+ for( int i=0; i<n.nOuts(); i++ )
+ if( n.out(i) != null ) return true;
+ return false; // The parser's keep is not a graph use.
+ }
+
+ @Override protected String edgeName(Node n, int i) {
+ return n instanceof ScopeNode scope && i < scope._vars.size()
+ ? scope.var(i)._name : null;
+ }
+ @Override protected int edgeJump(Node n, int i) {
+ return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret
+ ? ref(ret.fun()) : 0;
+ }
+ @Override protected int projectionIndex(Node n) { return n instanceof Proj p ? p.idx() : -1; }
+
+ @Override protected boolean folding(Node n) {
+ if( n instanceof FunNode fun ) return fun.folding();
+ if( n instanceof ReturnNode ret ) return ret.fun().folding();
+ if( n instanceof CallEndNode cend ) return cend.folding();
+ if( n instanceof CallNode )
+ for( int i=0; i<n.nOuts(); i++ )
+ if( n.out(i) instanceof CallEndNode cend && cend.folding() ) return true;
+ return false;
+ }
+
+ @Override protected Kind kind(Node n) {
+ if( n instanceof StopNode ) return Kind.STOP;
+ if( n instanceof ScopeNode ) return Kind.SCOPE;
+ if( n instanceof StartCUNode ) return Kind.UNIT;
+ if( n instanceof FunNode ) return Kind.FUN;
+ // Start inherits Loop for whole-program analysis, but is not a source loop.
+ if( n instanceof StartNode ) return Kind.START;
+ if( n instanceof LoopNode ) return Kind.LOOP;
+ if( n instanceof RegionNode ) return Kind.REGION;
+ if( n instanceof PhiNode ) return Kind.PHI;
+ if( n instanceof CFGNode ) return Kind.CTRL;
+ return isMem(n) ? Kind.MEM : Kind.DATA;
+ }
+
+ @Override protected Role role(Node n, int i) {
+ if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode ||
+ n instanceof CtrlNode || n instanceof XCtrlNode )
+ return Role.ASSOC;
+ if( n instanceof PhiNode )
+ return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
+ if( n instanceof Proj )
+ return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
+ // StartCU inherits Region, but slot 0 really connects it to Start.
+ if( n instanceof StartCUNode && i == 0 ) return Role.CTRL;
+ if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
+ // Preserve known slot roles even when an input is not attached/typed yet.
+ if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
+ if( n instanceof MemMergeNode ||
+ (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode)) ||
+ (n instanceof EscapeNode && i >= 2) ) return Role.MEM;
+ Node def = n.in(i);
+ if( def instanceof CFGNode ) return Role.CTRL;
+ if( def != null && isMem(def) ) return Role.MEM;
+ return Role.DATA;
+ }
+
+ private boolean isMem(Node n) {
+ // These Phi subclasses are memory even before their first type computation.
+ return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
new file mode 100644
index 00000000..f2ed2f72
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.graph.GraphCapture;
+import com.seaofnodes.graph.GraphViewer;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.Node;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/** Chapter 25's compilation context; capture and event bookkeeping are shared. */
+public class SimpleGraphObserver extends GraphCapture<Node> {
+ private CodeGen _code;
+ public SimpleGraphObserver() { super(new SimpleGraphAdapter()); }
+ public static void main(String[] args) throws IOException {
+ GraphViewer.run(new SimpleGraphObserver());
+ }
+ @Override protected void compile(String src) {
+ _code = new CodeGen(src);
+ _code._obs = this;
+ _code.driver(CodeGen.Phase.TypeCheck);
+ }
+ @Override protected String phase() { return _code._phase.name(); }
+ @Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; }
+ @Override protected void detach() {
+ if( _code != null ) _code._obs = null;
+ _code = null;
+ }
+ @Override protected Node scope() { return _code.P._scope; }
+ @Override protected void roots(ArrayList<Node> roots) {
+ roots.add(_code._stop);
+ if( _code._phase == CodeGen.Phase.Parse ) {
+ roots.addAll(_code.P._xScopes);
+ }
+ }
+}