From 29fb671e69f90ad3b04c6fa8d746caf857f89e77 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Tue, 22 Sep 2026 11:25:05 -0700 Subject: Add shared graph viewer to all chapters --- .github/workflows/linearize.sh | 4 +- build-support/early-chapter.mk | 7 +- chapter01/Makefile | 2 +- chapter01/pom.xml | 3 + .../main/java/com/seaofnodes/simple/Parser.java | 3 + .../simple/print/SimpleGraphAdapter.java | 45 + .../simple/print/SimpleGraphObserver.java | 35 + chapter02/Makefile | 2 +- chapter02/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 117 -- .../main/java/com/seaofnodes/simple/Parser.java | 9 + .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 45 + .../simple/print/SimpleGraphObserver.java | 37 + .../java/com/seaofnodes/simple/Chapter02Test.java | 4 - chapter03/Makefile | 2 +- chapter03/docs/03-grammar.md | 4 - chapter03/docs/03-graph1.gv | 2 +- chapter03/docs/03-graph2.gv | 2 +- chapter03/docs/03-graph3.gv | 2 +- chapter03/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 177 -- .../main/java/com/seaofnodes/simple/Parser.java | 25 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 47 + .../simple/print/SimpleGraphObserver.java | 38 + .../java/com/seaofnodes/simple/Chapter02Test.java | 4 - .../java/com/seaofnodes/simple/Chapter03Test.java | 4 +- chapter04/Makefile | 11 +- chapter04/README.md | 4 +- chapter04/docs/04-grammar.md | 4 - chapter04/docs/04-post-peephole.gv | 2 +- chapter04/docs/04-pre-peephole.gv | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 212 --- .../main/java/com/seaofnodes/simple/Parser.java | 19 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 6 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../java/com/seaofnodes/simple/print/JSViewer.java | 26 - .../simple/print/SimpleGraphAdapter.java | 2 +- .../simple/print/SimpleGraphObserver.java | 26 +- .../java/com/seaofnodes/simple/Chapter02Test.java | 4 - .../java/com/seaofnodes/simple/Chapter03Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 20 +- chapter05/Makefile | 2 +- chapter05/docs/05-grammar.md | 4 - chapter05/docs/05-graph1.gv | 4 +- chapter05/docs/05-graph2.gv | 4 +- chapter05/docs/05-graph3.gv | 4 +- chapter05/docs/05-graph8.gv | 2 +- chapter05/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 235 --- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 52 + .../simple/print/SimpleGraphObserver.java | 40 + .../java/com/seaofnodes/simple/Chapter02Test.java | 4 - .../java/com/seaofnodes/simple/Chapter03Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 20 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 20 +- chapter06/Makefile | 8 +- chapter06/docs/06-grammar.md | 4 - chapter06/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 230 --- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 52 + .../simple/print/SimpleGraphObserver.java | 40 + .../java/com/seaofnodes/simple/Chapter05Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 30 +- chapter07/Makefile | 8 +- chapter07/docs/07-grammar.md | 4 - chapter07/docs/07-graph1.gv | 2 +- chapter07/docs/07-graph2.gv | 2 +- chapter07/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 254 --- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 53 + .../simple/print/SimpleGraphObserver.java | 40 + chapter08/Makefile | 8 +- chapter08/docs/08-grammar.md | 4 - chapter08/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 256 --- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 53 + .../simple/print/SimpleGraphObserver.java | 40 + .../java/com/seaofnodes/simple/Chapter06Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 2 +- chapter09/Makefile | 8 +- chapter09/docs/09-grammar.md | 4 - chapter09/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 262 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 3 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 55 + .../simple/print/SimpleGraphObserver.java | 43 + chapter10/Makefile | 8 +- chapter10/docs/10-grammar.md | 4 - chapter10/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 269 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 3 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 3 - .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 3 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 61 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/SchedulerTest.java | 2 +- chapter11/Makefile | 8 +- chapter11/docs/11-grammar.md | 4 - chapter11/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 280 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 3 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 3 - .../main/java/com/seaofnodes/simple/node/Node.java | 27 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 3 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 62 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- chapter12/Makefile | 8 +- chapter12/docs/12-grammar.md | 4 - chapter12/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 280 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 13 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 3 - .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 3 - .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 3 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 - .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 62 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/Chapter12Test.java | 8 +- .../java/com/seaofnodes/simple/SchedulerTest.java | 2 +- chapter13/Makefile | 8 +- chapter13/docs/13-grammar.md | 4 - chapter13/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 280 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 2 - .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 3 - .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 9 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 1 - .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 62 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/Chapter10Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter11Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 16 +- chapter14/Makefile | 8 +- chapter14/docs/14-grammar.md | 2 - chapter14/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 280 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 2 - .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 3 - .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 9 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 1 - .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 62 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/Chapter10Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter11Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter14Test.java | 38 +- chapter15/Makefile | 8 +- chapter15/docs/15-grammar.md | 2 - chapter15/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 281 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 9 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 63 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/BrainFuckTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 12 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 2 +- chapter16/Makefile | 8 +- chapter16/docs/16-grammar.md | 2 - chapter16/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 288 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 23 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 9 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 63 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/BrainFuckTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 2 +- chapter17/Makefile | 8 +- chapter17/docs/17-grammar.md | 2 - chapter17/pom.xml | 3 + .../com/seaofnodes/simple/GraphVisualizer.java | 288 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 24 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 3 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 29 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StopNode.java | 9 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 63 + .../simple/print/SimpleGraphObserver.java | 43 + .../java/com/seaofnodes/simple/BrainFuckTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 2 +- chapter18/Makefile | 12 +- chapter18/README.md | 2 +- chapter18/docs/18-grammar.md | 2 - chapter18/pom.xml | 3 + .../main/java/com/seaofnodes/simple/CodeGen.java | 12 +- .../com/seaofnodes/simple/GraphVisualizer.java | 288 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 9 +- .../main/java/com/seaofnodes/simple/JSViewer.java | 410 ---- .../main/java/com/seaofnodes/simple/Parser.java | 14 +- .../com/seaofnodes/simple/SimpleWebSocket.java | 192 -- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 5 - .../com/seaofnodes/simple/node/ConstantNode.java | 2 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 36 + chapter19/Makefile | 12 +- chapter19/docs/18-grammar.md | 2 - chapter19/pom.xml | 3 + .../main/java/com/seaofnodes/simple/CodeGen.java | 12 +- .../com/seaofnodes/simple/GraphVisualizer.java | 288 --- .../main/java/com/seaofnodes/simple/IterPeeps.java | 9 +- .../main/java/com/seaofnodes/simple/JSViewer.java | 418 ---- .../main/java/com/seaofnodes/simple/Parser.java | 14 +- .../com/seaofnodes/simple/SimpleWebSocket.java | 192 -- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConstantNode.java | 2 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 27 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + chapter20/Makefile | 10 +- chapter20/docs/18-grammar.md | 2 - chapter20/pom.xml | 3 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 15 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 12 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConstantNode.java | 2 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 287 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 421 ---- .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + .../seaofnodes/simple/print/SimpleWebSocket.java | 193 -- chapter21/Makefile | 10 +- chapter21/docs/18-grammar.md | 2 - chapter21/pom.xml | 3 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 15 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 12 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AddNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConstantNode.java | 2 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../java/com/seaofnodes/simple/node/DivNode.java | 2 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/MulNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 2 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../java/com/seaofnodes/simple/node/SubNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 287 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 421 ---- .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + .../seaofnodes/simple/print/SimpleWebSocket.java | 193 -- .../test/java/com/seaofnodes/simple/Simple.java | 59 +- chapter22/Makefile | 27 +- chapter22/docs/18-grammar.md | 2 - chapter22/pom.xml | 3 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 11 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 12 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/ArithNode.java | 1 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConstantNode.java | 2 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../com/seaofnodes/simple/node/ExternNode.java | 1 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 3 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 287 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 421 ---- .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + .../seaofnodes/simple/print/SimpleWebSocket.java | 193 -- .../test/java/com/seaofnodes/simple/Simple.java | 58 +- chapter23/Makefile | 27 +- chapter23/docs/18-grammar.md | 2 - chapter23/pom.xml | 3 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 11 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 12 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 2 - .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/ArithNode.java | 1 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConFldOffNode.java | 1 - .../com/seaofnodes/simple/node/ConstantNode.java | 1 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 - .../com/seaofnodes/simple/node/ExternNode.java | 1 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/MulFNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 3 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 2 - .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 287 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 421 ---- .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + .../seaofnodes/simple/print/SimpleWebSocket.java | 193 -- .../test/java/com/seaofnodes/simple/Simple.java | 58 +- chapter24/Makefile | 27 +- chapter24/docs/18-grammar.md | 2 - chapter24/pom.xml | 3 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 11 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 12 +- .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/ArithNode.java | 1 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConFldOffNode.java | 1 - .../com/seaofnodes/simple/node/ConstantNode.java | 1 - .../com/seaofnodes/simple/node/ExternNode.java | 1 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 2 - .../java/com/seaofnodes/simple/node/MinusNode.java | 2 - .../java/com/seaofnodes/simple/node/NewNode.java | 6 +- .../main/java/com/seaofnodes/simple/node/Node.java | 25 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 3 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 287 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 421 ---- .../simple/print/SimpleGraphAdapter.java | 64 + .../simple/print/SimpleGraphObserver.java | 35 + .../seaofnodes/simple/print/SimpleWebSocket.java | 193 -- .../test/java/com/seaofnodes/simple/Simple.java | 58 +- chapter25/Makefile | 10 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 15 +- .../java/com/seaofnodes/simple/node/AndNode.java | 2 - .../java/com/seaofnodes/simple/node/ArithNode.java | 1 - .../java/com/seaofnodes/simple/node/BoolNode.java | 6 - .../com/seaofnodes/simple/node/ConFldOffNode.java | 1 - .../com/seaofnodes/simple/node/ConstantNode.java | 1 - .../com/seaofnodes/simple/node/ExternNode.java | 1 - .../com/seaofnodes/simple/node/FldOffNode.java | 1 - .../com/seaofnodes/simple/node/FunPtrNode.java | 1 - .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 1 - .../java/com/seaofnodes/simple/node/NewNode.java | 5 +- .../main/java/com/seaofnodes/simple/node/Node.java | 8 +- .../java/com/seaofnodes/simple/node/NotNode.java | 2 - .../java/com/seaofnodes/simple/node/OrNode.java | 3 - .../java/com/seaofnodes/simple/node/ParmNode.java | 2 - .../java/com/seaofnodes/simple/node/PhiNode.java | 2 - .../com/seaofnodes/simple/node/PtrToIntNode.java | 1 - .../com/seaofnodes/simple/node/RoundF32Node.java | 2 - .../java/com/seaofnodes/simple/node/SarNode.java | 2 - .../java/com/seaofnodes/simple/node/ShlNode.java | 2 - .../java/com/seaofnodes/simple/node/ShrNode.java | 2 - .../java/com/seaofnodes/simple/node/StoreNode.java | 4 +- .../com/seaofnodes/simple/node/ToFloatNode.java | 2 - .../java/com/seaofnodes/simple/node/XorNode.java | 2 - .../seaofnodes/simple/print/GraphVisualizer.java | 288 --- .../java/com/seaofnodes/simple/print/JSViewer.java | 26 - .../simple/print/SimpleGraphObserver.java | 21 +- .../test/java/com/seaofnodes/simple/Simple.java | 3 - graph/README.md | 97 +- graph/graph.mk | 11 + .../java/com/seaofnodes/graph/GraphCapture.java | 31 +- .../java/com/seaofnodes/graph/GraphViewer.java | 45 +- graph/test_browser.py | 40 +- graph/test_transport.py | 53 +- graph/web/index.html | 5 +- graph/web/legacy.js | 25 - graph/web/vendor/d3-graphviz.js | 2014 -------------------- graph/web/vendor/graphviz.umd.js | 7 - graph/web/viewer.js | 42 +- .../seaofnodes/simple/transpile/AstVisitor.java | 15 - .../seaofnodes/simple/transpile/TestMethod.java | 2 - .../simple/transpile/printers/RustPrinter.java | 6 - 715 files changed, 3639 insertions(+), 14968 deletions(-) create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java delete mode 100644 chapter04/src/main/java/com/seaofnodes/simple/print/JSViewer.java delete mode 100644 chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter14/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter15/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter16/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/GraphVisualizer.java create mode 100644 chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java delete mode 100644 chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java delete mode 100644 chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java create mode 100644 chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter19/src/main/java/com/seaofnodes/simple/GraphVisualizer.java delete mode 100644 chapter19/src/main/java/com/seaofnodes/simple/JSViewer.java delete mode 100644 chapter19/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java create mode 100644 chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java delete mode 100644 chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java delete mode 100644 chapter22/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter22/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter22/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java delete mode 100644 chapter23/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter23/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter23/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java delete mode 100644 chapter24/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter24/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter24/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java delete mode 100644 chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java delete mode 100644 chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java create mode 100644 graph/graph.mk delete mode 100644 graph/web/legacy.js delete mode 100644 graph/web/vendor/d3-graphviz.js delete mode 100644 graph/web/vendor/graphviz.umd.js diff --git a/.github/workflows/linearize.sh b/.github/workflows/linearize.sh index d4292fe2..ea3499b5 100755 --- a/.github/workflows/linearize.sh +++ b/.github/workflows/linearize.sh @@ -47,7 +47,7 @@ while read -r chapter; do done <<< "$all_authors" # Get the first and last dates this chapter was modified. - root_files=(README.md LICENSE pom.xml .gitignore .dir-locals.el transpile-tests graph) + root_files=(README.md LICENSE pom.xml .gitignore .dir-locals.el transpile-tests graph build-support) author_root_files=() if [[ $chapter = chapter01 ]]; then # Only include the commit date of the root files for chapter01. @@ -71,7 +71,7 @@ while read -r chapter; do git add . # Add the shared files in the root, except for README.md and pom.xml. - root_files=(LICENSE .gitignore .dir-locals.el transpile-tests graph) + root_files=(LICENSE .gitignore .dir-locals.el transpile-tests graph build-support) cp -R "${root_files[@]/#/"$repo/"}" . git add "${root_files[@]}" diff --git a/build-support/early-chapter.mk b/build-support/early-chapter.mk index 939b7782..5f82b5b0 100644 --- a/build-support/early-chapter.mk +++ b/build-support/early-chapter.mk @@ -17,6 +17,11 @@ jars := lib/junit-4.12.jar$(SEP)lib/hamcrest-core-1.3.jar main_classes := $(CLZDIR)/main/.mtag test_classes := $(CLZDIR)/test/.ttag +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) + +.PHONY: build +build: $(main_classes) + $(main_classes): $(main_javas) @mkdir -p $(CLZDIR)/main javac $(JAVAC_ARGS) -d $(CLZDIR)/main $(main_javas) @@ -42,4 +47,4 @@ lib/hamcrest-core-1.3.jar: @mkdir -p lib wget -O $@ https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) diff --git a/chapter01/Makefile b/chapter01/Makefile index 86e9ebba..67307a26 100644 --- a/chapter01/Makefile +++ b/chapter01/Makefile @@ -1 +1 @@ -include ../build-support/early-chapter.mk +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) diff --git a/chapter01/pom.xml b/chapter01/pom.xml index 08af80e1..5151e8c4 100644 --- a/chapter01/pom.xml +++ b/chapter01/pom.xml @@ -11,4 +11,7 @@ chapter01 jar Chapter 1 + + ${project.basedir}/../graph + diff --git a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java index 806c985d..c2ea3734 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java @@ -11,6 +11,9 @@ import com.seaofnodes.simple.node.*; */ public class Parser { + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode diff --git a/chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..59275832 --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 1's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return false; } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n._outputs.get(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = null; + String label = n instanceof ConstantNode c ? Long.toString(c._value) : n.getClass().getSimpleName(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + null, kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof StartNode ) return Kind.CTRL; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ConstantNode ) return Role.ASSOC; + if( i == 0 ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter01/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..1c78e4dd --- /dev/null +++ b/chapter01/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.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 1's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_ret); + } +} diff --git a/chapter02/Makefile b/chapter02/Makefile index 86e9ebba..67307a26 100644 --- a/chapter02/Makefile +++ b/chapter02/Makefile @@ -1 +1 @@ -include ../build-support/early-chapter.mk +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) diff --git a/chapter02/pom.xml b/chapter02/pom.xml index acc963b0..6fc4f177 100644 --- a/chapter02/pom.xml +++ b/chapter02/pom.xml @@ -11,4 +11,7 @@ chapter02 jar Chapter 2 + + ${project.basedir}/../graph + diff --git a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 4c625be1..00000000 --- a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter02 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Walk the Node edges - nodeEdges(sb, all); - - sb.append("}\n"); - return sb.toString(); - } - - - private void nodes(StringBuilder sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n.isCFG() ) - sb.append("shape=box style=filled fillcolor=yellow "); - sb.append("label=\"").append(lab).append("\" "); - sb.append("];\n"); - } - sb.append("\t}\n"); // End Node cluster - } - - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // In this chapter we do display the Constant->Start edge; - int i=0; - for( Node def : n._inputs ) { - if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - if( n instanceof ConstantNode && def instanceof StartNode ) - sb.append(" style=dotted"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - sb.append("];\n"); - } - i++; - } - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final StartNode start = Parser.START; - final HashMap all = new HashMap<>(); - for( Node n : start._outputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - if (c != null) - walk(all, c); - for (Node c : n._outputs) - if (c != null) - walk(all, c); - } -} diff --git a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java index 72da8eeb..f59b0f25 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -12,6 +14,12 @@ import com.seaofnodes.simple.type.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -27,6 +35,7 @@ public class Parser { public Parser(String source) { _lexer = new Lexer(source); + PARSER = this; Node.reset(); START = new StartNode(); } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java index 7c1968c5..814b99b8 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,8 +7,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java index e3c3616a..fc893e27 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -8,8 +8,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 0d59ef25..b83c41a5 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -8,8 +8,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java index 26c28281..b2b2f9c0 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -8,8 +8,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java index fd233108..7e278173 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -66,13 +68,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -178,7 +176,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -196,12 +194,22 @@ public abstract class Node { *
  • we ask the Node for a better replacement (again, none enabled in this chapter)
  • * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) { @@ -215,8 +223,8 @@ public abstract class Node { Node n = idealize(); if( n != null ) return n; - return this; // No progress - } + return null; // No progress + } /** diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java index d2e32c61..1d2ddaa0 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -8,8 +8,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..502a2a8e --- /dev/null +++ b/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 2's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof StartNode ) return Kind.CTRL; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ConstantNode ) return Role.ASSOC; + if( i == 0 ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..a17bf796 --- /dev/null +++ b/chapter02/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 2's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_ret); + } +} diff --git a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 36e62f9b..ba9acea7 100644 --- a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -14,8 +14,6 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); ReturnNode ret = parser.parse(); assertEquals("return (1+((2*3)+(-5)));", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); Node._disablePeephole = false; } @@ -59,8 +57,6 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); ReturnNode ret = parser.parse(); assertEquals("return 2;", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); } } diff --git a/chapter03/Makefile b/chapter03/Makefile index 86e9ebba..67307a26 100644 --- a/chapter03/Makefile +++ b/chapter03/Makefile @@ -1 +1 @@ -include ../build-support/early-chapter.mk +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) diff --git a/chapter03/docs/03-grammar.md b/chapter03/docs/03-grammar.md index 14a7d5fa..1d36b532 100644 --- a/chapter03/docs/03-grammar.md +++ b/chapter03/docs/03-grammar.md @@ -12,12 +12,8 @@ statement | declStatement | blockStatment | expressionStatement - | metaStatement ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter03/docs/03-graph1.gv b/chapter03/docs/03-graph1.gv index 2bdca351..7008a44e 100644 --- a/chapter03/docs/03-graph1.gv +++ b/chapter03/docs/03-graph1.gv @@ -1,6 +1,6 @@ digraph chapter03 { /* -int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph; +int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; */ rankdir=BT; ordering="in"; diff --git a/chapter03/docs/03-graph2.gv b/chapter03/docs/03-graph2.gv index 460b63c8..cafa7f25 100644 --- a/chapter03/docs/03-graph2.gv +++ b/chapter03/docs/03-graph2.gv @@ -1,6 +1,6 @@ digraph chapter03 { /* -int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph; +int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; */ rankdir=BT; ordering="in"; diff --git a/chapter03/docs/03-graph3.gv b/chapter03/docs/03-graph3.gv index 50cb6d27..791a7dc1 100644 --- a/chapter03/docs/03-graph3.gv +++ b/chapter03/docs/03-graph3.gv @@ -1,6 +1,6 @@ digraph chapter03 { /* -int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph; +int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); */ rankdir=BT; ordering="in"; diff --git a/chapter03/pom.xml b/chapter03/pom.xml index 2b977285..db9f8f88 100644 --- a/chapter03/pom.xml +++ b/chapter03/pom.xml @@ -11,4 +11,7 @@ chapter03 jar Chapter 3 + + ${project.basedir}/../graph + diff --git a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 9dfac52d..00000000 --- a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter03 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - scopes(sb, parser._scope); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the Scope edges - scopeEdges(sb, parser._scope); - - sb.append("}\n"); - return sb.toString(); - } - - - private void nodes(StringBuilder sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ScopeNode ) - continue; // Do not emit, rolled into Scope cluster already - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n.isCFG() ) - sb.append("shape=box style=filled fillcolor=yellow "); - sb.append("label=\"").append(lab).append("\" "); - sb.append("];\n"); - } - sb.append("\t}\n"); // End Node cluster - } - - private void scopes( StringBuilder sb, ScopeNode scopenode) { - sb.append("\tnode [shape=plaintext];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - for( String name : scope.keySet() ) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // In this chapter we do display the Constant->Start edge; - // ScopeNodes are done separately - if( n instanceof ScopeNode ) - continue; - int i=0; - for( Node def : n._inputs ) { - if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - if( n instanceof ConstantNode && def instanceof StartNode ) - sb.append(" style=dotted"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - sb.append("];\n"); - } - i++; - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - for( String name : scope.keySet() ) { - Node def = scopenode.in(scope.get(name)); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final StartNode start = Parser.START; - final HashMap all = new HashMap<>(); - for( Node n : start._outputs ) - walk(all, n); - // Scan symbol tables - for( HashMap scope : parser._scope._scopes ) - for (Integer i : scope.values()) - walk(all, parser._scope.in(i)); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - if (c != null) - walk(all, c); - for (Node c : n._outputs) - if (c != null) - walk(all, c); - } -} diff --git a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java index fa1a852c..e6cb84f8 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -43,6 +51,7 @@ public class Parser { public Parser(String source) { _lexer = new Lexer(source); + PARSER = this; Node.reset(); _scope = new ScopeNode(); START = new StartNode(); @@ -50,11 +59,9 @@ public class Parser { String src() { return new String( _lexer._input ); } - public ReturnNode parse() { return parse(false); } - public ReturnNode parse(boolean show) { + public ReturnNode parse() { var ret = (ReturnNode) parseBlock(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); - if( show ) showGraph(); return ret; } @@ -73,7 +80,7 @@ public class Parser { Node n = null; while (!peek('}') && !_lexer.isEOF()) { Node n0 = parseStatement(); - if (n0 != null) n = n0; // Allow null returns from eg showGraph + if (n0 != null) n = n0; // Empty statements can return null }; // Exit scope _scope.pop(); @@ -92,7 +99,6 @@ public class Parser { if (matchx("return") ) return parseReturn(); else if (matchx("int")) return parseDecl(); else if (match ("{" )) return require(parseBlock(),"}"); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -110,14 +116,7 @@ public class Parser { return new ReturnNode(START, expr).peephole(); } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java index 7c1968c5..814b99b8 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,8 +7,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java index e3c3616a..fc893e27 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -8,8 +8,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 0d59ef25..b83c41a5 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -8,8 +8,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java index 26c28281..b2b2f9c0 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -8,8 +8,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java index 6e4617ff..3922533c 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -66,13 +68,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -211,7 +209,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -229,12 +227,22 @@ public abstract class Node { *
  • we ask the Node for a better replacement (again, none enabled in this chapter)
  • * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) { @@ -248,8 +256,8 @@ public abstract class Node { Node n = idealize(); if( n != null ) return n; - return this; // No progress - } + return null; // No progress + } /** * This function needs to be diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java index d2e32c61..1d2ddaa0 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -8,8 +8,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..a615741d --- /dev/null +++ b/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,47 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 3's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( i == 0 ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..0e24e15d --- /dev/null +++ b/chapter03/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 3's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser._scope); + roots.add(_ret); + } +} diff --git a/chapter03/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter03/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 60ee8874..9de09d21 100644 --- a/chapter03/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter03/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -14,8 +14,6 @@ public class Chapter02Test { Node._disablePeephole = true; // disable peephole so we can observe full graph ReturnNode ret = parser.parse(); assertEquals("return (1+((2*3)+(-5)));", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); Node._disablePeephole = false; } @@ -59,8 +57,6 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); ReturnNode ret = parser.parse(); assertEquals("return 2;", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); } } diff --git a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 3b8b3449..b42f1433 100644 --- a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -30,7 +30,7 @@ public class Chapter03Test { @Test public void testVarScopeNoPeephole() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;"); + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; "); Node._disablePeephole = true; ReturnNode ret = parser.parse(); Node._disablePeephole = false; @@ -39,7 +39,7 @@ public class Chapter03Test { @Test public void testVarDist() { - Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;"); + Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); "); ReturnNode ret = parser.parse(); assertEquals("return 8;", ret.print()); } diff --git a/chapter04/Makefile b/chapter04/Makefile index 931ad8ef..060e0cfd 100644 --- a/chapter04/Makefile +++ b/chapter04/Makefile @@ -36,10 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) -# Compile the common adapter into this chapter without copying its sources. -GRAPH_DIR := $(firstword $(wildcard ../graph graph)) -graph_javas := $(wildcard $(GRAPH_DIR)/src/main/java/com/seaofnodes/graph/*.java) -main_javas += $(graph_javas) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -81,10 +78,6 @@ fuzzer: $(default_targets) @echo "fuzzing " $(test_cp) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap -.PHONY: view -view: build - java -ea -cp "$(CLZDIR)/main" com.seaofnodes.simple.print.JSViewer - .PHONY: clean clean: @@ -109,4 +102,4 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) diff --git a/chapter04/README.md b/chapter04/README.md index 272d0b8d..2c7f7e9c 100644 --- a/chapter04/README.md +++ b/chapter04/README.md @@ -108,7 +108,7 @@ tests can (and do) pass in other integer values by calling `new Parser` with an argument: ```java -Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2)); +Parser parser = new Parser("return arg; ", TypeInteger.constant(2)); ``` The graph: @@ -151,7 +151,7 @@ public final Node peephole( ) { If you don't pass an argument you get `TypeInteger.BOT` ```java - Parser parser = new Parser("return arg; #showGraph;"); + Parser parser = new Parser("return arg; "); ... public Parser(String source) { diff --git a/chapter04/docs/04-grammar.md b/chapter04/docs/04-grammar.md index 48facd4c..0600679e 100644 --- a/chapter04/docs/04-grammar.md +++ b/chapter04/docs/04-grammar.md @@ -12,12 +12,8 @@ statement | declStatement | blockStatment | expressionStatement - | metaStatement ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter04/docs/04-post-peephole.gv b/chapter04/docs/04-post-peephole.gv index 0a9f8e93..b893874f 100644 --- a/chapter04/docs/04-post-peephole.gv +++ b/chapter04/docs/04-post-peephole.gv @@ -1,6 +1,6 @@ digraph chapter04 { /* -return 1+arg+2; #showGraph; +return 1+arg+2; */ rankdir=BT; ordering="in"; diff --git a/chapter04/docs/04-pre-peephole.gv b/chapter04/docs/04-pre-peephole.gv index aefd81c4..d0d7cb0c 100644 --- a/chapter04/docs/04-pre-peephole.gv +++ b/chapter04/docs/04-pre-peephole.gv @@ -1,6 +1,6 @@ digraph chapter04 { /* -return 1+arg+2; #showGraph; +return 1+arg+2; */ rankdir=BT; ordering="in"; diff --git a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 62b434f3..00000000 --- a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter04 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - scopes(sb, parser._scope); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the Scope edges - scopeEdges(sb, parser._scope); - - sb.append("}\n"); - return sb.toString(); - } - - - private void nodes(StringBuilder sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof ScopeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if( n instanceof MultiNode ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for( Node use : n._outputs ) { - if( use instanceof ProjNode proj ) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n.isCFG() ) - sb.append("shape=box style=filled fillcolor=yellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - sb.append("\t}\n"); // End Node cluster - } - - private void scopes( StringBuilder sb, ScopeNode scopenode) { - sb.append("\tnode [shape=plaintext];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - for( String name : scope.keySet() ) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - int i=0; - for( Node def : n._inputs ) { - if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - sb.append("];\n"); - } - i++; - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - for( String name : scope.keySet() ) { - Node def = scopenode.in(scope.get(name)); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final StartNode start = Parser.START; - final HashMap all = new HashMap<>(); - for( Node n : start._outputs ) - walk(all, n); - // Scan symbol tables - for( HashMap scope : parser._scope._scopes ) - for (Integer i : scope.values()) - walk(all, parser._scope.in(i)); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - if (c != null) - walk(all, c); - for (Node c : n._outputs) - if (c != null) - walk(all, c); - } -} diff --git a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java index 360a7be9..487afee9 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java @@ -27,7 +27,6 @@ public class Parser { // Current compilation context, like START; the observer belongs to the parser. public static Parser PARSER; public GraphObserver _obs; - public ReturnNode _ret; public int pos() { return _lexer._position; } // The Lexer. Thin wrapper over a byte[] buffer with a cursor. @@ -73,8 +72,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public ReturnNode parse() { return parse(false); } - public ReturnNode parse(boolean show) { + public ReturnNode parse() { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, new ProjNode(START, 0, ScopeNode.CTRL).peephole()); @@ -82,9 +80,6 @@ public class Parser { var ret = (ReturnNode) parseBlock(); _scope.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); - if( show ) showGraph(); - _ret = ret; - if( _obs != null ) _obs.phase("Parse"); return ret; } @@ -103,7 +98,7 @@ public class Parser { Node n = null; while (!peek('}') && !_lexer.isEOF()) { Node n0 = parseStatement(); - if (n0 != null) n = n0; // Allow null returns from eg showGraph + if (n0 != null) n = n0; // Empty statements can return null }; // Exit scope _scope.pop(); @@ -122,7 +117,6 @@ public class Parser { if (matchx("return") ) return parseReturn(); else if (matchx("int")) return parseDecl(); else if (match ("{" )) return require(parseBlock(),"}"); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -143,14 +137,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java index 7e08a207..f568416c 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,8 +7,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 05495790..af0d22b7 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -14,9 +14,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java index 3b3a4436..c7276710 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -8,8 +8,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 0d59ef25..b83c41a5 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -8,8 +8,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java index 36d5eb3e..68ba9025 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -8,8 +8,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java index 58516151..eb7d8333 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java @@ -67,13 +67,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java index e3683646..83c63244 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -8,8 +8,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java index 58d757ea..c12e7388 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -8,8 +8,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter04/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index 43b0be2f..00000000 --- a/chapter04/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.*; -import com.seaofnodes.simple.Parser; -import java.io.IOException; - -public class JSViewer extends GraphViewer { - public static void main(String[] args) throws IOException { - try( var view = new JSViewer() ) { view.run(); } - } - JSViewer() throws IOException {} - - @Override protected void compile(String src) { - Parser parser = new Parser(src); - parser._obs = new SimpleGraphObserver(parser) { - @Override protected void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException { - JSViewer.this.frame(snap, pos, evt); - } - }; - try { - parser.parse(); - } finally { - parser._obs = null; - } - } -} diff --git a/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 72a11a87..a0ad7be0 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -17,7 +17,7 @@ public class SimpleGraphAdapter extends GraphAdapter { @Override protected Node out(Node n, int idx) { return n.out(idx); } private ArrayList edges(Node n) { - String[] names = n instanceof ScopeNode scope ? scope.reverseNames() : null; + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; var edges = new ArrayList(); for( int i = 0; i < n.nIns(); i++ ) { Role role = n instanceof ScopeNode || n instanceof ConstantNode ? Role.ASSOC diff --git a/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 865d1e20..f2ca7888 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -1,20 +1,36 @@ package com.seaofnodes.simple.print; import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.Node; import java.util.ArrayList; +import java.io.IOException; /** Chapter 4 needs only a parser; it has no worklist or distant dependencies. */ -public abstract class SimpleGraphObserver extends GraphCapture { - private final Parser _parser; - protected SimpleGraphObserver(Parser parser) { super(new SimpleGraphAdapter()); _parser = parser; } +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _ret = _parser.parse(); + phase("Parse"); + } @Override protected String phase() { return "Parse"; } @Override protected int pos() { return _parser.pos(); } - @Override protected void detach() { _parser._obs = null; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } @Override protected void roots(ArrayList roots) { roots.add(Parser.START); roots.add(_parser._scope); - roots.add(_parser._ret); + roots.add(_ret); } } diff --git a/chapter04/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter04/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 60ee8874..9de09d21 100644 --- a/chapter04/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter04/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -14,8 +14,6 @@ public class Chapter02Test { Node._disablePeephole = true; // disable peephole so we can observe full graph ReturnNode ret = parser.parse(); assertEquals("return (1+((2*3)+(-5)));", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); Node._disablePeephole = false; } @@ -59,8 +57,6 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); ReturnNode ret = parser.parse(); assertEquals("return 2;", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); } } diff --git a/chapter04/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter04/src/test/java/com/seaofnodes/simple/Chapter03Test.java index fc8e97b6..6a5d7072 100644 --- a/chapter04/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter04/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -31,7 +31,7 @@ public class Chapter03Test { @Test public void testVarScopeNoPeephole() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;"); + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; "); Node._disablePeephole = true; ReturnNode ret = parser.parse(); Node._disablePeephole = false; @@ -40,7 +40,7 @@ public class Chapter03Test { @Test public void testVarDist() { - Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;"); + Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); "); ReturnNode ret = parser.parse(); assertEquals("return 8;", ret.print()); } diff --git a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java index faae0aee..28912d5d 100644 --- a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -10,7 +10,7 @@ public class Chapter04Test { @Test public void testPeephole() { - Parser parser = new Parser("return 1+arg+2; #showGraph;"); + Parser parser = new Parser("return 1+arg+2; "); ReturnNode ret = parser.parse(); assertEquals("return (arg+3);", ret.print()); } @@ -38,7 +38,7 @@ public class Chapter04Test { @Test public void testPeephole3() { - Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); + Parser parser = new Parser("return 1+arg+2+arg+3; "); ReturnNode ret = parser.parse(); assertEquals("return ((arg*2)+6);", ret.print()); } @@ -52,7 +52,7 @@ public class Chapter04Test { @Test public void testVarArg() { - Parser parser = new Parser("return arg; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return arg; ", TypeInteger.BOT); ReturnNode ret = parser.parse(); assertTrue(ret.in(0) instanceof ProjNode); assertTrue(ret.in(1) instanceof ProjNode); @@ -60,49 +60,49 @@ public class Chapter04Test { @Test public void testConstantArg() { - Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2)); + Parser parser = new Parser("return arg; ", TypeInteger.constant(2)); ReturnNode ret = parser.parse(); assertEquals("return 2;", ret.print()); } @Test public void testCompEq() { - Parser parser = new Parser("return 3==3; #showGraph;"); + Parser parser = new Parser("return 3==3; "); ReturnNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testCompEq2() { - Parser parser = new Parser("return 3==4; #showGraph;"); + Parser parser = new Parser("return 3==4; "); ReturnNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testCompNEq() { - Parser parser = new Parser("return 3!=3; #showGraph;"); + Parser parser = new Parser("return 3!=3; "); ReturnNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testCompNEq2() { - Parser parser = new Parser("return 3!=4; #showGraph;"); + Parser parser = new Parser("return 3!=4; "); ReturnNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testBug1() { - Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; #showGraph;"); + Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; "); ReturnNode ret = parser.parse(); assertEquals("return (arg+3);", ret.print()); } @Test public void testBug2() { - Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;"); + Parser parser = new Parser("int a=arg+1; a=a; return a; "); ReturnNode ret = parser.parse(); assertEquals("return (arg+1);", ret.print()); } diff --git a/chapter05/Makefile b/chapter05/Makefile index 86e9ebba..67307a26 100644 --- a/chapter05/Makefile +++ b/chapter05/Makefile @@ -1 +1 @@ -include ../build-support/early-chapter.mk +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) diff --git a/chapter05/docs/05-grammar.md b/chapter05/docs/05-grammar.md index 65be4bf7..8eba1581 100644 --- a/chapter05/docs/05-grammar.md +++ b/chapter05/docs/05-grammar.md @@ -13,16 +13,12 @@ statement | blockStatment | expressionStatement | ifStatement - | metaStatement ; ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter05/docs/05-graph1.gv b/chapter05/docs/05-graph1.gv index 539ee7f2..b5cf8370 100644 --- a/chapter05/docs/05-graph1.gv +++ b/chapter05/docs/05-graph1.gv @@ -5,9 +5,9 @@ if (arg == 1) a = arg+2; else { a = arg-3; - #showGraph; + } -#showGraph; + return a; */ diff --git a/chapter05/docs/05-graph2.gv b/chapter05/docs/05-graph2.gv index 12506e85..a560bc49 100644 --- a/chapter05/docs/05-graph2.gv +++ b/chapter05/docs/05-graph2.gv @@ -5,9 +5,9 @@ if (arg == 1) a = arg+2; else { a = arg-3; - #showGraph; + } -#showGraph; + return a; */ rankdir=BT; diff --git a/chapter05/docs/05-graph3.gv b/chapter05/docs/05-graph3.gv index 277bed48..688c5ca9 100644 --- a/chapter05/docs/05-graph3.gv +++ b/chapter05/docs/05-graph3.gv @@ -5,9 +5,9 @@ if (arg == 1) a = arg+2; else { a = arg-3; - #showGraph; + } -#showGraph; + return a; */ rankdir=BT; diff --git a/chapter05/docs/05-graph8.gv b/chapter05/docs/05-graph8.gv index 2af43847..8f28207c 100644 --- a/chapter05/docs/05-graph8.gv +++ b/chapter05/docs/05-graph8.gv @@ -11,7 +11,7 @@ else if( arg==3 ) else a=5; return a; -#showGraph; + */ rankdir=BT; ordering="in"; diff --git a/chapter05/pom.xml b/chapter05/pom.xml index 49991f74..d952e5ca 100644 --- a/chapter05/pom.xml +++ b/chapter05/pom.xml @@ -11,4 +11,7 @@ chapter05 jar Chapter 5 + + ${project.basedir}/../graph + diff --git a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index c35a4e32..00000000 --- a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter05 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - for (ScopeNode sn: parser._xScopes) - scopes(sb, sn); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the Scope edges - for (ScopeNode sn: parser._xScopes) - scopeEdges(sb, sn); - - sb.append("}\n"); - return sb.toString(); - } - - - private void nodes(StringBuilder sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof ScopeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if( n instanceof MultiNode ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for( Node use : n._outputs ) { - if( use instanceof ProjNode proj ) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n.isCFG() ) - sb.append("shape=box style=filled fillcolor=yellow "); - if( n instanceof PhiNode ) - sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - - // Force Region & Phis to line up - for( Node n : all ) { - if( n instanceof RegionNode region ) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";") ; - for( Node phi : region._outputs ) - if( phi instanceof PhiNode ) - sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - - sb.append("\t}\n"); // End Node cluster - } - - private void scopes( StringBuilder sb, ScopeNode scopenode) { - sb.append("\tnode [shape=plaintext];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - for( String name : scope.keySet() ) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - int i=0; - for( Node def : n._inputs ) { - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - sb.append("];\n"); - } - i++; - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - for( String name : scope.keySet() ) { - Node def = scopenode.in(scope.get(name)); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final StartNode start = Parser.START; - final HashMap all = new HashMap<>(); - for( Node n : start._outputs ) - walk(all, n); - // Scan symbol tables - for( HashMap scope : parser._scope._scopes ) - for (Integer i : scope.values()) - walk(all, parser._scope.in(i)); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - if (c != null) - walk(all, c); - for (Node c : n._outputs) - if (c != null) - walk(all, c); - } -} diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java index 80674d59..dbaa093d 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -62,6 +70,7 @@ public class Parser { public final Stack _xScopes = new Stack<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); @@ -85,8 +94,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -97,7 +105,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -134,7 +141,6 @@ public class Parser { else if (matchx("int")) return parseDecl(); else if (match ("{" )) return require(parseBlock(),"}"); else if (matchx("if" )) return parseIf(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -202,14 +208,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java index 62d1e249..0600222a 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,8 +7,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 7a4a087e..f5db6574 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -14,9 +14,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java index 519a8c8a..1bb04be7 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -8,8 +8,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 0d59ef25..b83c41a5 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -8,8 +8,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java index 2dbdd695..46ee6058 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -8,8 +8,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java index 8681ac5a..d2c83761 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -66,13 +68,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -202,7 +200,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive @@ -233,12 +231,22 @@ public abstract class Node { * code. * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) @@ -252,8 +260,8 @@ public abstract class Node { // Recursively optimize return deadCodeElim(n.peephole()); - return this; // No progress - } + return null; // No progress + } // m is the new Node, self is the old. // Return 'm', which may have zero uses but is alive nonetheless. diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java index e3683646..83c63244 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -8,8 +8,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 374e1183..95c0fdf9 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -10,8 +10,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb) { sb.append("Phi("); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java index 475b3790..2e170273 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -8,8 +8,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..2ec62125 --- /dev/null +++ b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,52 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 5's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..e88a7ffe --- /dev/null +++ b/chapter05/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 5's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 4085ed7e..47cee096 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -14,8 +14,6 @@ public class Chapter02Test { Node._disablePeephole = true; // disable peephole so we can observe full graph StopNode ret = parser.parse(); assertEquals("return (1+((2*3)+(-5)));", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); Node._disablePeephole = false; } @@ -59,8 +57,6 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); StopNode ret = parser.parse(); assertEquals("return 2;", ret.print()); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); } } diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter03Test.java index adbfba84..64ff153e 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -31,7 +31,7 @@ public class Chapter03Test { @Test public void testVarScopeNoPeephole() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;"); + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; "); Node._disablePeephole = true; StopNode ret = parser.parse(); Node._disablePeephole = false; @@ -40,7 +40,7 @@ public class Chapter03Test { @Test public void testVarDist() { - Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;"); + Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); "); StopNode ret = parser.parse(); assertEquals("return 8;", ret.print()); } diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 55c0b15f..2e2abb25 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -10,7 +10,7 @@ public class Chapter04Test { @Test public void testPeephole() { - Parser parser = new Parser("return 1+arg+2; #showGraph;"); + Parser parser = new Parser("return 1+arg+2; "); StopNode ret = parser.parse(); assertEquals("return (arg+3);", ret.print()); } @@ -38,7 +38,7 @@ public class Chapter04Test { @Test public void testPeephole3() { - Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); + Parser parser = new Parser("return 1+arg+2+arg+3; "); StopNode ret = parser.parse(); assertEquals("return ((arg*2)+6);", ret.print()); } @@ -52,7 +52,7 @@ public class Chapter04Test { @Test public void testVarArg() { - Parser parser = new Parser("return arg; #showGraph;"); + Parser parser = new Parser("return arg; "); StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof ProjNode); @@ -61,49 +61,49 @@ public class Chapter04Test { @Test public void testConstantArg() { - Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2)); + Parser parser = new Parser("return arg; ", TypeInteger.constant(2)); StopNode ret = parser.parse(); assertEquals("return 2;", ret.print()); } @Test public void testCompEq() { - Parser parser = new Parser("return 3==3; #showGraph;"); + Parser parser = new Parser("return 3==3; "); StopNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testCompEq2() { - Parser parser = new Parser("return 3==4; #showGraph;"); + Parser parser = new Parser("return 3==4; "); StopNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testCompNEq() { - Parser parser = new Parser("return 3!=3; #showGraph;"); + Parser parser = new Parser("return 3!=3; "); StopNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testCompNEq2() { - Parser parser = new Parser("return 3!=4; #showGraph;"); + Parser parser = new Parser("return 3!=4; "); StopNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testBug1() { - Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; #showGraph;"); + Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; "); StopNode ret = parser.parse(); assertEquals("return (arg+3);", ret.print()); } @Test public void testBug2() { - Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;"); + Parser parser = new Parser("int a=arg+1; a=a; return a; "); StopNode ret = parser.parse(); assertEquals("return (arg+1);", ret.print()); } diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java index b5fcbeaf..512d0d50 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -17,11 +17,11 @@ if (arg == 1) a = arg+2; else { a = arg-3; - #showGraph; + } -#showGraph; + return a;"""); - StopNode ret = parser.parse(true); + StopNode ret = parser.parse(); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } @@ -36,7 +36,7 @@ if (arg == 1) { c = 4; } return c;""", TypeInteger.BOT); - StopNode ret = parser.parse(true); + StopNode ret = parser.parse(); assertEquals("return Phi(Region16,4,3);", ret.toString()); } @@ -48,7 +48,7 @@ if( arg==1 ) return 3; else return 4; -#showGraph;""", TypeInteger.BOT); +""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } @@ -64,7 +64,7 @@ if( arg==1 ) else b=a+1; return a+b;"""); - StopNode ret = parser.parse(true); + StopNode ret = parser.parse(); assertEquals("return ((arg*2)+Phi(Region20,2,3));", ret.toString()); } @@ -79,7 +79,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - StopNode ret = parser.parse(true); + StopNode ret = parser.parse(); assertEquals("return ((Phi(Region31,(arg*2),arg)+arg)+Phi(Region31,4,5));", ret.toString()); } @@ -98,7 +98,7 @@ else if( arg==3 ) else a=5; return a; -#showGraph;""", TypeInteger.BOT); +""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("return Phi(Region33,Phi(Region21,2,3),Phi(Region31,4,5));", stop.toString()); } @@ -114,7 +114,7 @@ if( arg ) if( arg==0 ) b=2; return arg+a+b; -#showGraph;""", TypeInteger.BOT); +""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("return ((arg+Phi(Region13,1,0))+Phi(Region22,2,0));", stop.toString()); } @@ -129,7 +129,7 @@ if( arg==1 ) a=arg==3; } return a;"""); - StopNode ret = parser.parse(true); + StopNode ret = parser.parse(); assertEquals("return (arg==Phi(Region16,3,2));", ret.toString()); } diff --git a/chapter06/Makefile b/chapter06/Makefile index cb362b42..8c2d5797 100644 --- a/chapter06/Makefile +++ b/chapter06/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(test_javas)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -92,4 +93,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter06/docs/06-grammar.md b/chapter06/docs/06-grammar.md index 4cf1cac6..9ed7b160 100644 --- a/chapter06/docs/06-grammar.md +++ b/chapter06/docs/06-grammar.md @@ -13,16 +13,12 @@ statement | blockStatment | expressionStatement | ifStatement - | metaStatement ; ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter06/pom.xml b/chapter06/pom.xml index fb852b3d..fa76937e 100644 --- a/chapter06/pom.xml +++ b/chapter06/pom.xml @@ -11,4 +11,7 @@ chapter06 jar Chapter 6 + + ${project.basedir}/../graph + diff --git a/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index b30dd450..00000000 --- a/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter06 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - for (ScopeNode sn: parser._xScopes) - scopes(sb, sn); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the Scope edges - for (ScopeNode sn: parser._xScopes) - scopeEdges(sb, sn); - - sb.append("}\n"); - return sb.toString(); - } - - - private void nodes(StringBuilder sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof ScopeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if( n instanceof MultiNode ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for( Node use : n._outputs ) { - if( use instanceof ProjNode proj ) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n.isCFG() ) - sb.append("shape=box style=filled fillcolor=yellow "); - if( n instanceof PhiNode ) - sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - - // Force Region & Phis to line up - for( Node n : all ) { - if( n instanceof RegionNode region ) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";") ; - for( Node phi : region._outputs ) - if( phi instanceof PhiNode ) - sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - - sb.append("\t}\n"); // End Node cluster - } - - private void scopes( StringBuilder sb, ScopeNode scopenode) { - sb.append("\tnode [shape=plaintext];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - for( String name : scope.keySet() ) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - int i=0; - for( Node def : n._inputs ) { - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - sb.append("];\n"); - } - i++; - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(scopenode, level); - for( String name : scope.keySet() ) { - Node def = scopenode.in(scope.get(name)); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final HashMap all = new HashMap<>(); - for( Node n : Parser.START._outputs ) - walk(all, n); - for( Node n : parser._scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java index 1a15efa3..9b79c195 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -62,6 +70,7 @@ public class Parser { public final Stack _xScopes = new Stack<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); @@ -85,8 +94,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -97,7 +105,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -134,7 +141,6 @@ public class Parser { else if (matchx("int")) return parseDecl(); else if (match ("{" )) return require(parseBlock(),"}"); else if (matchx("if" )) return parseIf(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -203,14 +209,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java index af2a41b3..1cc20608 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,8 +7,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 79c65c60..24bca613 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -14,9 +14,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java index 519a8c8a..1bb04be7 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -8,8 +8,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 0d59ef25..b83c41a5 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -8,8 +8,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java index 0380c079..20ec7d87 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -8,8 +8,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java index 44e8dfd2..b533ae34 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -76,13 +78,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -223,7 +221,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive @@ -254,12 +252,22 @@ public abstract class Node { * code. * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) @@ -273,8 +281,8 @@ public abstract class Node { // Recursively optimize return deadCodeElim(n.peephole()); - return this; // No progress - } + return null; // No progress + } // m is the new Node, self is the old. // Return 'm', which may have zero uses but is alive nonetheless. diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java index e3683646..83c63244 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -8,8 +8,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 15088e08..f7cf917e 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -10,8 +10,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb) { sb.append("Phi("); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java index 475b3790..2e170273 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -8,8 +8,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..cee75f6a --- /dev/null +++ b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,52 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 6's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..122c7267 --- /dev/null +++ b/chapter06/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 6's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter06/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter06/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 134a8be2..31ca56a0 100644 --- a/chapter06/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter06/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -19,7 +19,7 @@ else { a = arg-3; } return a;"""); - StopNode ret = parser.parse(false); + StopNode ret = parser.parse(); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } @@ -34,7 +34,7 @@ if (arg == 1) { c = 4; } return c;""", TypeInteger.BOT); - StopNode ret = parser.parse(false); + StopNode ret = parser.parse(); assertEquals("return Phi(Region16,4,3);", ret.toString()); } @@ -62,7 +62,7 @@ if( arg==1 ) else b=a+1; return a+b;"""); - StopNode ret = parser.parse(false); + StopNode ret = parser.parse(); assertEquals("return ((arg*2)+Phi(Region20,2,3));", ret.toString()); } @@ -77,7 +77,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - StopNode ret = parser.parse(false); + StopNode ret = parser.parse(); assertEquals("return ((Phi(Region31,(arg*2),arg)+arg)+Phi(Region31,4,5));", ret.toString()); } @@ -127,7 +127,7 @@ if( arg==1 ) a=arg==3; } return a;"""); - StopNode ret = parser.parse(false); + StopNode ret = parser.parse(); assertEquals("return (arg==Phi(Region16,3,2));", ret.toString()); } diff --git a/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 44b49d73..cb58f2bb 100644 --- a/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -15,7 +15,7 @@ public class Chapter06Test { if( true ) return 2; return 1; """); - StopNode stop = parser.parse(false); + StopNode stop = parser.parse(); assertEquals("return 2;", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -29,7 +29,7 @@ if (arg) a = 2; return (arg < a) < 3; """); - StopNode stop = parser.parse(false); + StopNode stop = parser.parse(); assertEquals("return ((argchapter07 jar Chapter 7 + + ${project.basedir}/../graph + diff --git a/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 07f1af21..00000000 --- a/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter07 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - for( ScopeNode scope : parser._xScopes ) - scope( sb, scope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - for( ScopeNode scope : parser._xScopes ) - scopeEdges( sb, scope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof ScopeNode) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final HashMap all = new HashMap<>(); - for( Node n : Parser.START._outputs ) - walk(all, n); - for( Node n : parser._scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java index cfb4deb4..f40eb3a3 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -63,6 +71,7 @@ public class Parser { public final Stack _xScopes = new Stack<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); @@ -86,8 +95,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -98,7 +106,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -136,7 +143,6 @@ public class Parser { else if (match ("{" )) return require(parseBlock(),"}"); else if (matchx("if" )) return parseIf(); else if (matchx("while")) return parseWhile(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -273,14 +279,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java index 85d9a593..f80467b9 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 5948a1d3..3938b903 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -16,9 +16,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java index 7207fd48..e8b7067e 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 3d944fd4..e4d718d5 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java index 2e701a4e..18e23d8b 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java index 49c1e6a1..0cc9f208 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -76,13 +78,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -256,7 +254,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive @@ -301,12 +299,22 @@ public abstract class Node { * code. * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) @@ -320,8 +328,8 @@ public abstract class Node { // Recursively optimize return deadCodeElim(n.peephole()); - return this; // No progress - } + return null; // No progress + } // m is the new Node, self is the old. // Return 'm', which may have zero uses but is alive nonetheless. diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java index 39df410d..e8d2ecf2 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -10,8 +10,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 2a4ff52a..46646773 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -12,8 +12,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( region().inProgress() ) diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java index af66b950..5e61df95 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..e907e744 --- /dev/null +++ b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,53 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 7's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..1f4987d9 --- /dev/null +++ b/chapter07/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 7's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter08/Makefile b/chapter08/Makefile index 742a9aef..fcecddfa 100644 --- a/chapter08/Makefile +++ b/chapter08/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -92,4 +93,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter08/docs/08-grammar.md b/chapter08/docs/08-grammar.md index 4c61bfd7..00f93e04 100644 --- a/chapter08/docs/08-grammar.md +++ b/chapter08/docs/08-grammar.md @@ -16,7 +16,6 @@ statement | whileStatement | breakStatement | continueStatment - | metaStatement ; whileStatement @@ -35,9 +34,6 @@ ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter08/pom.xml b/chapter08/pom.xml index 738653fe..40fd6883 100644 --- a/chapter08/pom.xml +++ b/chapter08/pom.xml @@ -11,4 +11,7 @@ chapter08 jar Chapter 8 + + ${project.basedir}/../graph + diff --git a/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index d75e5bea..00000000 --- a/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,256 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parser) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(parser); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter08 {\n"); - sb.append("/*\n"); - sb.append(parser.src()); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - for( ScopeNode scope : parser._xScopes ) - scope( sb, scope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - for( ScopeNode scope : parser._xScopes ) - scopeEdges( sb, scope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof ScopeNode) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Parser parser) { - final HashMap all = new HashMap<>(); - for( Node n : Parser.START._outputs ) - walk(all, n); - for( Node n : parser._scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java index 69796828..5c699a54 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -68,6 +76,7 @@ public class Parser { ScopeNode _breakScope; public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); @@ -92,8 +101,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -104,7 +112,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -144,7 +151,6 @@ public class Parser { else if (matchx("while")) return parseWhile(); else if (matchx("break")) return parseBreak(); else if (matchx("continue")) return parseContinue(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -320,14 +326,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); - return null; - } + /** * Parses an expression statement diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java index 0c66422d..7fa63426 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 5948a1d3..3938b903 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -16,9 +16,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java index 7207fd48..e8b7067e 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 3d944fd4..e4d718d5 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java index 2e701a4e..18e23d8b 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java index ba64b059..f20e9c2f 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -77,13 +79,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -258,7 +256,7 @@ public abstract class Node { } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive @@ -303,12 +301,22 @@ public abstract class Node { * code. * */ - public final Node peephole( ) { + public final Node peephole() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Type old = _type; + Node n = peepholeOpt(); + Node rez = n == null ? this : n; + if( obs != null ) obs.after(this, n == null && old == _type ? null : rez, false); + return rez; + } + + private Node peepholeOpt() { // Compute initial or improved Type Type type = _type = compute(); if (_disablePeephole) - return this; // Peephole optimizations turned off + return null; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) @@ -322,8 +330,8 @@ public abstract class Node { // Recursively optimize return deadCodeElim(n.peephole()); - return this; // No progress - } + return null; // No progress + } // m is the new Node, self is the old. // Return 'm', which may have zero uses but is alive nonetheless. diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java index 39df410d..e8d2ecf2 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -10,8 +10,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java index c7b5b357..7eeed394 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -12,8 +12,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( region().inProgress() ) diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java index af66b950..5e61df95 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..585592d4 --- /dev/null +++ b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,53 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 8's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n._outputs.get(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..7861b040 --- /dev/null +++ b/chapter08/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 8's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter08/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter08/src/test/java/com/seaofnodes/simple/Chapter06Test.java index b858b83e..00d725bc 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -29,7 +29,7 @@ if (arg) a = 2; return (arg < a) < 3; """); - StopNode stop = parser.parse(false); + StopNode stop = parser.parse(); assertEquals("return ((argchapter09 jar Chapter 9 + + ${project.basedir}/../graph + diff --git a/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index b4d64d4c..00000000 --- a/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,262 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter09 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // Preserve node input order - sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof ScopeNode) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // control edges are colored red - if( def.isCFG() ) - sb.append(" color=red"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java index 6dafd95f..41fc6916 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -59,7 +59,7 @@ public abstract class IterPeeps { /** * Iterate peepholes to a fixed point */ - public static StopNode iterate(StopNode stop, boolean show) { + public static StopNode iterate(StopNode stop) { assert progressOnList(stop); int cnt=0; @@ -67,9 +67,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,10 +95,9 @@ public abstract class IterPeeps { n.moveDepsToWorklist(); assert progressOnList(stop); // Very expensive assert } + if( obs != null ) obs.after(n, x, true); } - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); return stop; } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java index aabdf63a..393b5358 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -68,6 +76,7 @@ public class Parser { ScopeNode _breakScope; public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); _lexer = new Lexer(source); @@ -93,8 +102,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -105,7 +113,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -144,7 +151,6 @@ public class Parser { else if (matchx("while")) return parseWhile(); else if (matchx("break")) return parseBreak(); else if (matchx("continue")) return parseContinue(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";")) return null; // Empty statement else return parseExpressionStatement(); } @@ -318,14 +324,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java index 4e30bb55..557fb451 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java index ee643bf5..2000e905 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -16,9 +16,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java index 08ce70b8..d6ecfb69 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java index dd092c9e..cb79b15b 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java index 7e9f88a2..6a6c82f9 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java index 3524ca81..d47e4f32 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -80,13 +82,9 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -312,13 +310,14 @@ public abstract class Node { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -476,6 +475,8 @@ public abstract class Node { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list o dependencies. Only add it if its not @@ -486,6 +487,8 @@ public abstract class Node { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java index 7a520fec..9fce01bd 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -10,8 +10,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java index ec4c6ad3..13c9423d 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -12,8 +12,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java index 602feffc..6ed80df4 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -55,6 +55,5 @@ public class StopNode extends Node { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this,false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ); } + public StopNode iterate() { return IterPeeps.iterate(this); } } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java index f661be14..20c7e1cb 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..a3372f08 --- /dev/null +++ b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,55 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; + +/** Chapter 9's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + return Role.DATA; + } +} diff --git a/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..2f0b286c --- /dev/null +++ b/chapter09/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 9's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter10/Makefile b/chapter10/Makefile index ed0a03c9..77df7182 100644 --- a/chapter10/Makefile +++ b/chapter10/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter10/docs/10-grammar.md b/chapter10/docs/10-grammar.md index 10c80426..b76531d5 100644 --- a/chapter10/docs/10-grammar.md +++ b/chapter10/docs/10-grammar.md @@ -17,7 +17,6 @@ statement | whileStatement | breakStatement | continueStatment - | metaStatement ; field @@ -48,9 +47,6 @@ ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter10/pom.xml b/chapter10/pom.xml index 33a558e1..6c4500aa 100644 --- a/chapter10/pom.xml +++ b/chapter10/pom.xml @@ -11,4 +11,7 @@ chapter10 jar Chapter 10 + + ${project.basedir}/../graph + diff --git a/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index cfe6d5cc..00000000 --- a/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter10 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof ScopeNode) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof ProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java index 1cee1ccb..7075d526 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -57,7 +57,7 @@ public abstract class IterPeeps { /** * Iterate peepholes to a fixed point */ - public static StopNode iterate(StopNode stop, boolean show) { + public static StopNode iterate(StopNode stop) { assert progressOnList(stop); int cnt=0; @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,10 +95,9 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); return stop; } diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java index fc84c9d9..01c7da29 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -74,6 +82,7 @@ public class Parser { public static Map OBJS = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); OBJS.clear(); @@ -100,8 +109,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -112,7 +120,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -153,7 +160,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -370,14 +376,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java index 2cc0b465..b88e13b5 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 8719e4d5..ab577aa6 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java index 08ce70b8..d6ecfb69 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 53ae1096..ece27d86 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -28,9 +28,6 @@ public class LoadNode extends MemOpNode { @Override public String label() { return "Load"; } - @Override - public String glabel() { return "."+_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 9b687613..fa252903 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java index 7e9f88a2..6a6c82f9 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java index 352689b5..b13462f5 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -17,9 +17,6 @@ public class NewNode extends Node { @Override public String label() { return "new"; } - @Override - public String glabel() { return "new " + _ptr._obj._name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java index 718b930d..a035b60f 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -71,17 +73,11 @@ public abstract class Node { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -287,13 +283,14 @@ public abstract class Node { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -451,6 +448,8 @@ public abstract class Node { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -461,6 +460,8 @@ public abstract class Node { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java index 05e44eb0..3d85b220 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java index dd67bd06..bdf50847 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java index 3b5a7ce3..9cc0e476 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -57,8 +57,7 @@ public class StopNode extends Node { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this,false).typeCheck(); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ).typeCheck(); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 8d264601..cf586d6e 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,8 +24,6 @@ public class StoreNode extends MemOpNode { @Override public String label() { return "Store"; } @Override - public String glabel() { return "."+_name+" ="; } - @Override public boolean isMem() { return true; } public Node val() { return in(3); } diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java index b87115cd..8264b740 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..545897d8 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,61 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 10's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..2b59f7ba --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 10's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 5def2782..853999b1 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -31,7 +31,7 @@ if (arg) v.f=1; return i; """); StopNode._disablePeephole = true; - StopNode stop = parser.parse(true);//.iterate(); + StopNode stop = parser.parse();//.iterate(); var eval = new Evaluator(stop); assertEquals(2L, eval.evaluate(1, 10)); } diff --git a/chapter11/Makefile b/chapter11/Makefile index ed0a03c9..77df7182 100644 --- a/chapter11/Makefile +++ b/chapter11/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter11/docs/11-grammar.md b/chapter11/docs/11-grammar.md index 949ed70a..5a3f3778 100644 --- a/chapter11/docs/11-grammar.md +++ b/chapter11/docs/11-grammar.md @@ -17,7 +17,6 @@ statement | whileStatement | breakStatement | continueStatment - | metaStatement ; field @@ -48,9 +47,6 @@ ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter11/pom.xml b/chapter11/pom.xml index 7bbd7cb1..16536ba3 100644 --- a/chapter11/pom.xml +++ b/chapter11/pom.xml @@ -11,4 +11,7 @@ chapter11 jar Chapter 11 + + ${project.basedir}/../graph + diff --git a/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 604770a4..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java index b2b76777..9d2b913f 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -57,7 +57,7 @@ public abstract class IterPeeps { /** * Iterate peepholes to a fixed point */ - public static StopNode iterate(StopNode stop, boolean show) { + public static StopNode iterate(StopNode stop) { assert progressOnList(stop); int cnt=0; @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,10 +95,9 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); GlobalCodeMotion.buildCFG(stop); return stop; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java index e367f499..7008512f 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -80,6 +88,7 @@ public class Parser { public static Map OBJS = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); OBJS.clear(); @@ -109,8 +118,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -123,7 +131,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -164,7 +171,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -381,14 +387,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java index 2cc0b465..b88e13b5 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 8719e4d5..ab577aa6 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java index 08ce70b8..d6ecfb69 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 296237f4..39bbaff1 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -28,9 +28,6 @@ public class LoadNode extends MemOpNode { @Override public String label() { return "Load"; } - @Override - public String glabel() { return "."+_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 9b687613..fa252903 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java index 7e9f88a2..6a6c82f9 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java index c00fee62..1c82d5f5 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -17,9 +17,6 @@ public class NewNode extends Node { @Override public String label() { return "new"; } - @Override - public String glabel() { return "new " + _ptr._obj._name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java index e5bad801..5ebcf843 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java @@ -72,17 +72,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -287,13 +281,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -451,6 +446,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -461,6 +458,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java index 05e44eb0..3d85b220 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java index ddd0ca72..52f098fa 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java index 2bef4f50..da32651a 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -67,8 +67,7 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this,false).typeCheck(); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ).typeCheck(); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java index db8e2ce6..5b30b3ec 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,8 +24,6 @@ public class StoreNode extends MemOpNode { @Override public String label() { return "Store"; } @Override - public String glabel() { return "."+_name+" ="; } - @Override public boolean isMem() { return true; } public Node val() { return in(3); } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java index 5ddcdb68..515b21e8 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..38acb8cd --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 11's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..af20e147 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 11's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 32c9c0c5..853999b1 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -31,7 +31,7 @@ if (arg) v.f=1; return i; """); StopNode._disablePeephole = true; - StopNode stop = parser.parse(true);//.iterate(); + StopNode stop = parser.parse();//.iterate(); var eval = new Evaluator(stop); assertEquals(2L, eval.evaluate(1, 10)); } @@ -46,7 +46,7 @@ S v0=new S; if(arg) v0.f=1; return v0; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); var eval = new Evaluator(stop); assertObj(eval.evaluate(0, 10), "S", 0L); assertObj(eval.evaluate(1, 10), "S", 1L); @@ -65,7 +65,7 @@ i=v.f; if (arg) v.f=1; return i; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); var eval = new Evaluator(stop); assertEquals(2L, eval.evaluate(0, 10)); assertEquals(2L, eval.evaluate(1, 10)); diff --git a/chapter12/Makefile b/chapter12/Makefile index ed0a03c9..77df7182 100644 --- a/chapter12/Makefile +++ b/chapter12/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter12/docs/12-grammar.md b/chapter12/docs/12-grammar.md index f0fe2e40..4c071a98 100644 --- a/chapter12/docs/12-grammar.md +++ b/chapter12/docs/12-grammar.md @@ -17,7 +17,6 @@ statement | whileStatement | breakStatement | continueStatment - | metaStatement ; PRIMTYPE @@ -53,9 +52,6 @@ ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter12/pom.xml b/chapter12/pom.xml index c46bf13d..f70175a8 100644 --- a/chapter12/pom.xml +++ b/chapter12/pom.xml @@ -11,4 +11,7 @@ chapter12 jar Chapter 12 + + ${project.basedir}/../graph + diff --git a/chapter12/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter12/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 604770a4..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java index 121d0233..65a5e64f 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -57,7 +57,7 @@ public abstract class IterPeeps { /** * Iterate peepholes to a fixed point */ - public static StopNode iterate(StopNode stop, boolean show) { + public static StopNode iterate(StopNode stop) { assert progressOnList(stop); int cnt=0; @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,12 +95,12 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } // Break infinite loops, forcing a Never-branch to exit GlobalCodeMotion.fixLoops(stop); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); + GlobalCodeMotion.buildCFG(stop); return stop; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java index 1a223726..2bcd8da7 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -81,6 +89,7 @@ public class Parser { public static HashMap TYPES = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); TYPES.clear(); @@ -112,8 +121,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -126,7 +134,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -166,7 +173,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -384,14 +390,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java index 48350957..f51adcf1 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java index f202b263..2055ea6f 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -20,9 +20,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java index e0c95019..ae73284f 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 244a0919..a56678df 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -28,9 +28,6 @@ public class LoadNode extends MemOpNode { @Override public String label() { return "Load"; } - @Override - public String glabel() { return "."+_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java index ae8e9e1b..0f2669b9 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java index 8c8a6e0a..09a27582 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java index c00fee62..1c82d5f5 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -17,9 +17,6 @@ public class NewNode extends Node { @Override public String label() { return "new"; } - @Override - public String glabel() { return "new " + _ptr._obj._name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java index 352257a9..6b48799b 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -288,13 +284,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -452,6 +449,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -462,6 +461,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java index 3cf69520..e987bd82 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 02a4020f..dbe74f57 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java index fe96bd2a..c36bdf4d 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -67,8 +67,7 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this,false).typeCheck(); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ).typeCheck(); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java index db8e2ce6..5b30b3ec 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,8 +24,6 @@ public class StoreNode extends MemOpNode { @Override public String label() { return "Store"; } @Override - public String glabel() { return "."+_name+" ="; } - @Override public boolean isMem() { return true; } public Node val() { return in(3); } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java index f0c77743..c9d20ce0 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..8e909e1c --- /dev/null +++ b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 12's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..3d9ec14a --- /dev/null +++ b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 12's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 00b2591c..d862df95 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -29,7 +29,7 @@ public class Chapter12Test { """ return 3.14; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -40,7 +40,7 @@ return 3.14; """ return 3.14; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -57,7 +57,7 @@ while( 1 ) { } return guess; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); @@ -70,7 +70,7 @@ return guess; flt x = arg; return x+1==x; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter12/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 5def2782..853999b1 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -31,7 +31,7 @@ if (arg) v.f=1; return i; """); StopNode._disablePeephole = true; - StopNode stop = parser.parse(true);//.iterate(); + StopNode stop = parser.parse();//.iterate(); var eval = new Evaluator(stop); assertEquals(2L, eval.evaluate(1, 10)); } diff --git a/chapter13/Makefile b/chapter13/Makefile index ed0a03c9..77df7182 100644 --- a/chapter13/Makefile +++ b/chapter13/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter13/docs/13-grammar.md b/chapter13/docs/13-grammar.md index 00796552..6cbbf388 100644 --- a/chapter13/docs/13-grammar.md +++ b/chapter13/docs/13-grammar.md @@ -17,7 +17,6 @@ statement | whileStatement | breakStatement | continueStatment - | metaStatement ; PRIMTYPE @@ -54,9 +53,6 @@ ifStatement : 'if' '(' expression ')' statement ('else' statement)? ; -metaStatement - : '#showGraph' ';' - ; expressionStatement : IDENTIFIER '=' expression ';' diff --git a/chapter13/pom.xml b/chapter13/pom.xml index 93761369..138c3d2e 100644 --- a/chapter13/pom.xml +++ b/chapter13/pom.xml @@ -11,4 +11,7 @@ chapter13 jar Chapter 13 + + ${project.basedir}/../graph + diff --git a/chapter13/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter13/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 604770a4..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java index 783fc4f1..7075d526 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,6 +95,7 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } return stop; diff --git a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java index 71bd6d02..e41293bd 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -81,6 +89,7 @@ public class Parser { public static HashMap TYPES = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); TYPES.clear(); @@ -112,8 +121,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -126,7 +134,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -166,7 +173,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -385,14 +391,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java index 67eacd3a..1c4b94f8 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java index f202b263..2055ea6f 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -20,9 +20,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java index e0c95019..ae73284f 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java index a033a7cf..ff468b72 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,8 +23,6 @@ public class LoadNode extends MemOpNode { } @Override public String label() { return _name; } - @Override public String glabel() { return "."+_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java index ae8e9e1b..0f2669b9 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java index 16915a1c..e631492c 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/NewNode.java index c00fee62..1c82d5f5 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -17,9 +17,6 @@ public class NewNode extends Node { @Override public String label() { return "new"; } - @Override - public String glabel() { return "new " + _ptr._obj._name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java index c738205b..0d751617 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -288,13 +284,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -452,6 +449,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -462,6 +461,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/NotNode.java index 3cf69520..e987bd82 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 02a4020f..dbe74f57 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StopNode.java index d5b00011..aed4a8e6 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.GlobalCodeMotion; -import com.seaofnodes.simple.GraphVisualizer; import java.util.BitSet; @@ -69,18 +68,16 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this).typeCheck().GCM(false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); return this; } - StopNode GCM(boolean show) { + StopNode GCM() { // Break infinite loops, forcing a Never-branch to exit GlobalCodeMotion.fixLoops(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(this,null,null)); + GlobalCodeMotion.buildCFG(this); return this; } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 57e8b3ad..142aebc3 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,7 +25,6 @@ public class StoreNode extends MemOpNode { } @Override public String label() { return "." +_name+"="; } - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(3); } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java index 682930e0..d911ce7e 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java index 81cc6076..467d9181 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..60eae682 --- /dev/null +++ b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 13's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..ee333ad7 --- /dev/null +++ b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 13's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java index e790d34d..cfe677ec 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -81,7 +81,7 @@ if( (arg/13)==0 ) { int r = g+h; return p-r; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java index bdcae399..bb14f785 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -306,7 +306,7 @@ else { } } """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Stop[ return 9; return 0; return 0; return 0; return 0; ]", stop.toString()); } @@ -466,7 +466,7 @@ while (arg) { } return i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return .f;", stop.toString()); } @@ -486,7 +486,7 @@ while(arg) { } return arg; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop15,arg,Phi(Region37,.f,0));", stop.toString()); } @@ -504,7 +504,7 @@ if (arg) { } return v; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new S;", stop.toString()); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java index f9f3a5da..57455510 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -28,7 +28,7 @@ public class Chapter12Test { """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -39,7 +39,7 @@ return 3.14; """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -56,7 +56,7 @@ while( 1 ) { } return guess; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); @@ -69,7 +69,7 @@ return guess; flt x = arg; return x+1==x; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 59f08963..d695c9a6 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -14,7 +14,7 @@ public class Chapter13Test { """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -56,7 +56,7 @@ LLI? next = head.next; if( next==null ) return 1; return next.i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); assertEquals(2L, Evaluator.evaluate(stop, 3)); } @@ -75,7 +75,7 @@ i0.f = f0; f0.i = i0; return f0.i.f.i.i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 17;", stop.toString()); } @@ -87,7 +87,7 @@ struct N { N next; int i; } N n = new N; return n.next; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return null;", stop.toString()); } @@ -100,7 +100,7 @@ N n = new N; n.next = new N; return n.next; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new N;", stop.toString()); } @@ -149,7 +149,7 @@ return n.i; """ struct S{}; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); } @@ -173,7 +173,7 @@ struct S1 { S2? s; } struct S2 { int x; } return new S1.s=new S2; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new S1;", stop.toString()); } @@ -190,7 +190,7 @@ p2.pi.i = 2; if (arg) p1 = new P; return p1.pi.i + 1; """); - try { parser.parse(false).iterate(); fail(); } + try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } diff --git a/chapter14/Makefile b/chapter14/Makefile index ed0a03c9..77df7182 100644 --- a/chapter14/Makefile +++ b/chapter14/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter14/docs/14-grammar.md b/chapter14/docs/14-grammar.md index a6d4df42..f23f81cd 100644 --- a/chapter14/docs/14-grammar.md +++ b/chapter14/docs/14-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' fields '}' ; diff --git a/chapter14/pom.xml b/chapter14/pom.xml index fc308d63..3aaa2d40 100644 --- a/chapter14/pom.xml +++ b/chapter14/pom.xml @@ -11,4 +11,7 @@ chapter14 jar Chapter 14 + + ${project.basedir}/../graph + diff --git a/chapter14/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter14/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 604770a4..00000000 --- a/chapter14/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if (_separateControlCluster && doCtrl && !n.isCFG()) continue; - if (_separateControlCluster && !doCtrl && n.isCFG()) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { - var syms = scope._scopes.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._scopes.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def.isCFG() ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._scopes.size()-1; i>=0; i-- ) { - var syms = scope._scopes.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java index 783fc4f1..7075d526 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,6 +95,7 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } return stop; diff --git a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java index 9e4b9eb6..5d7d67da 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -93,6 +101,7 @@ public class Parser { public static HashMap TYPES = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); TYPES.clear(); @@ -136,8 +145,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -150,7 +158,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -190,7 +197,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -409,14 +415,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java index bd1a9dcd..31332722 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java index 350859db..8afb14b1 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -10,8 +10,6 @@ public class AndNode extends Node { @Override public String label() { return "And"; } - @Override public String glabel() { return "&"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/BoolNode.java index ae042f30..b504712f 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -21,9 +21,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java index da9571db..fd5fd1ed 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 78ccc8a9..2db5e469 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,8 +23,6 @@ public class LoadNode extends MemOpNode { } @Override public String label() { return _name; } - @Override public String glabel() { return "."+_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8ba45d26..27b4a478 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java index 6f679462..f521f2ce 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/NewNode.java index c00fee62..1c82d5f5 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -17,9 +17,6 @@ public class NewNode extends Node { @Override public String label() { return "new"; } - @Override - public String glabel() { return "new " + _ptr._obj._name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java index 3956ed6b..d917893d 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -286,13 +282,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -450,6 +447,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -460,6 +459,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/NotNode.java index b3c77c45..3ef5d19e 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java index e18996b7..fd5be78e 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -10,8 +10,6 @@ public class OrNode extends Node { @Override public String label() { return "Or"; } - @Override public String glabel() { return "|"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 42f8c86e..1d4b3fba 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+_label; } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter14/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java index 881a3812..6436cd64 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -10,8 +10,6 @@ public class SarNode extends Node { @Override public String label() { return "Sar"; } - @Override public String glabel() { return ">>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 605c5d86..1bed5264 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -10,8 +10,6 @@ public class ShlNode extends Node { @Override public String label() { return "Shl"; } - @Override public String glabel() { return "<<"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java index bed18d2b..26442c29 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -10,8 +10,6 @@ public class ShrNode extends Node { @Override public String label() { return "Shr"; } - @Override public String glabel() { return ">>>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/StopNode.java index d5b00011..aed4a8e6 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.GlobalCodeMotion; -import com.seaofnodes.simple.GraphVisualizer; import java.util.BitSet; @@ -69,18 +68,16 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this).typeCheck().GCM(false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); return this; } - StopNode GCM(boolean show) { + StopNode GCM() { // Break infinite loops, forcing a Never-branch to exit GlobalCodeMotion.fixLoops(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(this,null,null)); + GlobalCodeMotion.buildCFG(this); return this; } diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 57e8b3ad..142aebc3 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,7 +25,6 @@ public class StoreNode extends MemOpNode { } @Override public String label() { return "." +_name+"="; } - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(3); } diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java index 6d2f1711..45c4817c 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java index 3978f048..6b3bbec5 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -10,8 +10,6 @@ public class XorNode extends Node { @Override public String label() { return "Xor"; } - @Override public String glabel() { return "^"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..39f79e69 --- /dev/null +++ b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 14's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..5db8c01e --- /dev/null +++ b/chapter14/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 14's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 31220334..cfe677ec 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -81,7 +81,7 @@ if( (arg/13)==0 ) { int r = g+h; return p-r; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } @@ -101,7 +101,7 @@ bar.a = 1; bar.a = 2; return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); } @@ -155,7 +155,7 @@ while (arg) { } return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); } @@ -207,7 +207,7 @@ if (arg) bar = null; if( bar ) bar.a = 1; return bar; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region31,(*void)Phi(Region19,null,new Bar),null);", stop.toString()); } @@ -223,7 +223,7 @@ if( !bar ) rez=4; else bar.a = 1; return rez; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region37,4,3);", stop.toString()); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 4bfc25f4..58a11ba5 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -306,7 +306,7 @@ else { } } """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); } @@ -466,7 +466,7 @@ while (arg) { } return i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return .f;", stop.toString()); } @@ -486,7 +486,7 @@ while(arg) { } return arg; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop15,arg,Phi(Region37,.f,0));", stop.toString()); } @@ -504,7 +504,7 @@ if (arg) { } return v; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new S;", stop.toString()); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 61b1f490..c8802f4c 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -28,7 +28,7 @@ public class Chapter12Test { """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -39,7 +39,7 @@ return 3.14; """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -56,7 +56,7 @@ while( 1 ) { } return guess; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); @@ -69,7 +69,7 @@ return guess; flt x = arg; return x+1==x; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 807badd5..66ed1073 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -14,7 +14,7 @@ public class Chapter13Test { """ return 3.14; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -56,7 +56,7 @@ LLI? next = head.next; if( next==null ) return 1; return next.i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); assertEquals(2L, Evaluator.evaluate(stop, 3)); } @@ -75,7 +75,7 @@ i0.f = f0; f0.i = i0; return f0.i.f.i.i; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 17;", stop.toString()); } @@ -87,7 +87,7 @@ struct N { N next; int i; } N n = new N; return n.next; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return null;", stop.toString()); } @@ -100,7 +100,7 @@ N n = new N; n.next = new N; return n.next; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new N;", stop.toString()); } @@ -149,7 +149,7 @@ return n.i; """ struct S{}; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); } @@ -173,7 +173,7 @@ struct S1 { S2? s; } struct S2 { int x; } return new S1.s=new S2; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return new S1;", stop.toString()); } @@ -190,7 +190,7 @@ p2.pi.i = 2; if (arg) p1 = new P; return p1.pi.i + 1; """); - try { parser.parse(false).iterate(); fail(); } + try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java index fcb7fa24..c4768fb3 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -15,7 +15,7 @@ public class Chapter14Test { """ return 3.14; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } @@ -31,7 +31,7 @@ if( b < 0 ) c = -1; if( b > 2 ) c = 1; return c; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 99;", stop.toString()); assertEquals(99L, Evaluator.evaluate(stop, 0)); } @@ -44,7 +44,7 @@ u8 b = 123; b = b + 456;// Truncate return b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 67;", stop.toString()); assertEquals(67L, Evaluator.evaluate(stop, 0)); } @@ -58,7 +58,7 @@ u8 b = 123; while( b ) b = b + 456;// Truncate return b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop9,123,((Phi_b+456)&255));", stop.toString()); } @@ -71,7 +71,7 @@ b = b + 456;// Truncate u1 c = b; // No more truncate needed return c; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 1;", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); } @@ -84,7 +84,7 @@ int b = 123; b = b+456 & 31; // Precedence return b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 3;", stop.toString()); assertEquals(3L, Evaluator.evaluate(stop, 0)); } @@ -98,7 +98,7 @@ Foo f = new Foo; f.b = 123; return f.b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 1;", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); } @@ -110,7 +110,7 @@ return f.b; i8 b = 255; // Chopped return b; // Sign extend """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return -1;", stop.toString()); assertEquals(-1L, Evaluator.evaluate(stop, 0)); } @@ -123,7 +123,7 @@ i8 b = arg; b = b + 1;// Truncate return b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return (((((arg<<56)>>56)+1)<<56)>>56);", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); } @@ -136,7 +136,7 @@ u16 mask = (1<<16)-1; // AND mask int c = 123456789 & mask; return c; // """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 52501;", stop.toString()); assertEquals(52501L, Evaluator.evaluate(stop, 0)); } @@ -151,7 +151,7 @@ int j = -i; // Negating Long.MIN_VALUE wraps, cannot constant fold if (arg) j = 1; return j; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region28,1,(-((arg&9223372036854775807)+-9223372036854775808)));", stop.toString()); assertEquals(-9223372036854775808L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); @@ -163,7 +163,7 @@ return j; """ return (arg >>> 1)==0; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return (!(arg>>>1));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); @@ -176,7 +176,7 @@ return (arg >>> 1)==0; """ return (arg | 123 ^ 456) >>> 1; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return (((arg|123)^456)>>>1);", stop.toString()); assertEquals(217L, Evaluator.evaluate(stop, 0)); } @@ -208,7 +208,7 @@ if (arg) i = a.i; else i = b.i; return i; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region31,.i,.i);", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); } @@ -228,7 +228,7 @@ while (1) { return j+1; } """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return ((arg>>Phi(Region32,2,3))+1);", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); @@ -247,7 +247,7 @@ else b = f == 2.0; if (arg&5) b = arg == 1; return b; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region37,(arg==1),((flt)arg==Phi(Region23,1.0,2.0)));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); @@ -263,7 +263,7 @@ if (arg&7) i=3; else i=2; return (arg == i) == 1; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return (arg==Phi(Region18,3,2));", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); assertEquals(0L, Evaluator.evaluate(stop, 1)); @@ -282,7 +282,7 @@ while (1) { return i + -1; } """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return (Phi(Region43,((arg&3)+9223372036854775804),((arg&7)+9223372036854775800))+-1);", stop.toString()); assertEquals(0x7FFFFFFFFFFFFFF7L, Evaluator.evaluate(stop, 0)); assertEquals(0x7FFFFFFFFFFFFFFCL, Evaluator.evaluate(stop, 1)); @@ -309,7 +309,7 @@ f32 ff32 = 3.141592653589793; if( ff32 != 3.1415927410125732) return 5; return 0; """); - StopNode stop = parser.parse().iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); } diff --git a/chapter15/Makefile b/chapter15/Makefile index ed0a03c9..77df7182 100644 --- a/chapter15/Makefile +++ b/chapter15/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter15/docs/15-grammar.md b/chapter15/docs/15-grammar.md index ade6cb19..76ea0cab 100644 --- a/chapter15/docs/15-grammar.md +++ b/chapter15/docs/15-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' fields '}' ; diff --git a/chapter15/pom.xml b/chapter15/pom.xml index 2ec5dfbb..6e81a91e 100644 --- a/chapter15/pom.xml +++ b/chapter15/pom.xml @@ -11,4 +11,7 @@ chapter15 jar Chapter 15 + + ${project.basedir}/../graph + diff --git a/chapter15/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter15/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index f5fa1350..00000000 --- a/chapter15/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; - -import java.util.*; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._idxs.size()-1; idx>=0; idx-- ) { - var syms = scope._idxs.get(idx); - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - int scopeLevel = scope._idxs.size()-level; - sb.append("\t\t\t"); - for(String name: syms.keySet()) - sb.append(""); - sb.append("\n"); - sb.append("\t\t\t
    ").append(scopeLevel).append("").append(name).append("
    >];\n"); - level++; - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._idxs.size()-1; i>=0; i-- ) { - var syms = scope._idxs.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java index 783fc4f1..7075d526 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,6 +95,7 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } return stop; diff --git a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java index 4bb18d6b..71bf1b07 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -98,6 +106,7 @@ public class Parser { public static HashMap TYPES = new HashMap<>(); public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); TYPES.clear(); @@ -142,8 +151,7 @@ public class Parser { private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -159,7 +167,6 @@ public class Parser { _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -199,7 +206,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -420,14 +426,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** * Parses an expression statement or a declaration statement where type is a struct diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java index 97d2bb9b..e0d3d204 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java index 7e4b898b..d431c13e 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -10,8 +10,6 @@ public class AndNode extends Node { @Override public String label() { return "And"; } - @Override public String glabel() { return "&"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 06c10a79..5d2212b0 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -21,9 +21,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java index 76e98678..d13dcacf 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 0bdf86f0..99a6d72c 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -25,11 +25,8 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8a2c9406..7e95088f 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java index 6f679462..f521f2ce 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java index b65630bb..cf9dfbeb 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -285,13 +281,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole().keep()).unkeep(); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -449,6 +446,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. ArrayList _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -459,6 +458,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new ArrayList<>(); if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/NotNode.java index b3c77c45..3ef5d19e 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java index 1b32d4fb..24f59f20 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -10,8 +10,6 @@ public class OrNode extends Node { @Override public String label() { return "Or"; } - @Override public String glabel() { return "|"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java index ddca3034..36c0fe56 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter15/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java index ba99fde8..3143d417 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -10,8 +10,6 @@ public class SarNode extends Node { @Override public String label() { return "Sar"; } - @Override public String glabel() { return ">>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 28e6dd14..f4da5576 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -10,8 +10,6 @@ public class ShlNode extends Node { @Override public String label() { return "Shl"; } - @Override public String glabel() { return "<<"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 16d78ebf..ead27fb5 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -10,8 +10,6 @@ public class ShrNode extends Node { @Override public String label() { return "Shr"; } - @Override public String glabel() { return ">>>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StopNode.java index d5b00011..aed4a8e6 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.GlobalCodeMotion; -import com.seaofnodes.simple.GraphVisualizer; import java.util.BitSet; @@ -69,18 +68,16 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this).typeCheck().GCM(false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); return this; } - StopNode GCM(boolean show) { + StopNode GCM() { // Break infinite loops, forcing a Never-branch to exit GlobalCodeMotion.fixLoops(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(this,null,null)); + GlobalCodeMotion.buildCFG(this); return this; } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java index b1c74b94..ec7e0694 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,10 +25,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java index 009fb30e..9eb30089 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java index 30a88dca..e4ec1b06 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -10,8 +10,6 @@ public class XorNode extends Node { @Override public String label() { return "Xor"; } - @Override public String glabel() { return "^"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..639108ae --- /dev/null +++ b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,63 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 15's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..57ea2dba --- /dev/null +++ b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 15's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter15/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter15/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index d5ffe835..29ef8732 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -74,7 +74,7 @@ while (pc < program#) { } return output; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Hello World!\n", Evaluator.evaluate(stop, 0, 10000).toString()); } } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java index b737762e..589ae97f 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -82,7 +82,7 @@ if( (arg/13)==0 ) { int r = g+h; return p-r; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } @@ -102,7 +102,7 @@ bar.a = 1; bar.a = 2; return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); } @@ -118,7 +118,7 @@ else v.y = 3; return v; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Vector2D;", stop.toString()); } @@ -156,7 +156,7 @@ while (arg) { } return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString()); } @@ -208,7 +208,7 @@ if (arg) bar = null; if( bar ) bar.a = 1; return bar; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region33,(*void)Phi(Region20,null,Bar),null);", stop.toString()); } @@ -224,7 +224,7 @@ if( !bar ) rez=4; else bar.a = 1; return rez; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region39,4,3);", stop.toString()); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java index ab8ea902..daf981bd 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -190,7 +190,7 @@ p2.pi.i = 2; if (arg) p1 = new P; return p1.pi.i + 1; """); - try { parser.parse(false).iterate(); fail(); } + try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } diff --git a/chapter16/Makefile b/chapter16/Makefile index ed0a03c9..77df7182 100644 --- a/chapter16/Makefile +++ b/chapter16/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter16/docs/16-grammar.md b/chapter16/docs/16-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter16/docs/16-grammar.md +++ b/chapter16/docs/16-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter16/pom.xml b/chapter16/pom.xml index b5f79ff0..c6541455 100644 --- a/chapter16/pom.xml +++ b/chapter16/pom.xml @@ -11,4 +11,7 @@ chapter16 jar Chapter 16 + + ${project.basedir}/../graph + diff --git a/chapter16/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter16/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index de6d65a3..00000000 --- a/chapter16/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeMinNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - ScopeMinNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java index 96ae350e..cabc4a15 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,6 +95,7 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } return stop; diff --git a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java index a8064e17..b52767cc 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import static com.seaofnodes.simple.Utils.TODO; @@ -15,6 +17,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -103,6 +111,7 @@ public class Parser { public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); SCHEDULED = false; @@ -150,8 +159,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private Node ctrl(Node n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -174,7 +182,6 @@ public class Parser { INITS.clear(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -214,7 +221,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // declarations of vars with struct type are handled in parseExpressionStatement due // to ambiguity @@ -394,14 +400,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** Parse: name [=expr] */ diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java index 97d2bb9b..e0d3d204 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java index 17a4e0ca..8ab44858 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -10,8 +10,6 @@ public class AndNode extends Node { @Override public String label() { return "And"; } - @Override public String glabel() { return "&"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 06c10a79..5d2212b0 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -21,9 +21,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java index 76e98678..d13dcacf 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java index d15d4f83..60be3baa 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8a2c9406..7e95088f 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java index 949af27a..65898907 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java index ddb1e09a..77800be2 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -32,12 +32,8 @@ public class NewNode extends Node implements MultiNode { } @Override public String label() { - return "new_"+glabel(); + return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java index daf5994c..31db0f9f 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -292,13 +288,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -456,6 +453,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -466,6 +465,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/NotNode.java index ffd3e01e..9e4cc324 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java index b572346f..301514f4 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -10,8 +10,6 @@ public class OrNode extends Node { @Override public String label() { return "Or"; } - @Override public String glabel() { return "|"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 6cd9efb0..f533a7a4 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter16/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java index 5dc26ad3..25ef55e3 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -10,8 +10,6 @@ public class SarNode extends Node { @Override public String label() { return "Sar"; } - @Override public String glabel() { return ">>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java index b67d985a..1f558b8f 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -10,8 +10,6 @@ public class ShlNode extends Node { @Override public String label() { return "Shl"; } - @Override public String glabel() { return "<<"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 22eb45a0..e8ae2c1c 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -10,8 +10,6 @@ public class ShrNode extends Node { @Override public String label() { return "Shr"; } - @Override public String glabel() { return ">>>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/StopNode.java index d5b00011..aed4a8e6 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.GlobalCodeMotion; -import com.seaofnodes.simple.GraphVisualizer; import java.util.BitSet; @@ -69,18 +68,16 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this).typeCheck().GCM(false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); return this; } - StopNode GCM(boolean show) { + StopNode GCM() { // Break infinite loops, forcing a Never-branch to exit GlobalCodeMotion.fixLoops(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(this,null,null)); + GlobalCodeMotion.buildCFG(this); return this; } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 7b1bddd5..efa4dba5 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,10 +25,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java index 009fb30e..9eb30089 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java index 191933f9..71dce586 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -10,8 +10,6 @@ public class XorNode extends Node { @Override public String label() { return "Xor"; } - @Override public String glabel() { return "^"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..6d281535 --- /dev/null +++ b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,63 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 16's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..8ca8f7ad --- /dev/null +++ b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 16's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter16/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter16/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index d5ffe835..29ef8732 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -74,7 +74,7 @@ while (pc < program#) { } return output; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Hello World!\n", Evaluator.evaluate(stop, 0, 10000).toString()); } } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java index a40dbe90..2b07234f 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -81,7 +81,7 @@ if( (arg/13)==0 ) { int r = g+h; return p-r; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } @@ -101,7 +101,7 @@ bar.a = 1; bar.a = 2; return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); } @@ -117,7 +117,7 @@ else v.y = 3; return v; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Vector2D;", stop.toString()); } @@ -207,7 +207,7 @@ if (arg) bar = null; if( bar ) bar.a = 1; return bar; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region38,(*void)Phi(Region24,null,Bar),null);", stop.toString()); } @@ -223,7 +223,7 @@ if( !bar ) rez=4; else bar.a = 1; return rez; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region44,4,3);", stop.toString()); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 73fff8fd..aa496030 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -189,7 +189,7 @@ p2.pi.i = 2; if (arg) p1 = new P; return p1.pi.i + 1; """); - try { parser.parse(false).iterate(); fail(); } + try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } diff --git a/chapter17/Makefile b/chapter17/Makefile index ed0a03c9..77df7182 100644 --- a/chapter17/Makefile +++ b/chapter17/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -98,4 +99,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter17/docs/17-grammar.md b/chapter17/docs/17-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter17/docs/17-grammar.md +++ b/chapter17/docs/17-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter17/pom.xml b/chapter17/pom.xml index fbe22605..e6fe1140 100644 --- a/chapter17/pom.xml +++ b/chapter17/pom.xml @@ -11,4 +11,7 @@ chapter17 jar Chapter 17 + + ${project.basedir}/../graph + diff --git a/chapter17/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter17/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index de6d65a3..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter11 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeMinNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - ScopeMinNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - private Collection findAll(Node stop, Node scope) { - final HashMap all = new HashMap<>(); - for( Node n : stop._inputs ) - walk(all, n); - if( scope != null ) - for( Node n : scope._inputs ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java index 36f053f9..5a80698a 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,6 +95,7 @@ public abstract class IterPeeps { } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } return stop; diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java index 789eea19..a8157b41 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; @@ -14,6 +16,12 @@ import java.util.*; */ public class Parser { + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver _obs; + public int pos() { return _lexer._position; } + + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -102,6 +110,7 @@ public class Parser { public Parser(String source, TypeInteger arg) { + PARSER = this; Node.reset(); IterPeeps.reset(); SCHEDULED = false; @@ -151,8 +160,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); @@ -175,7 +183,6 @@ public class Parser { INITS.clear(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -216,7 +223,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Declaration or normal assignment/expression else return parseDeclarationStatement(); @@ -502,14 +508,7 @@ public class Parser { return ret; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ @@ -1181,7 +1180,6 @@ public class Parser { private boolean peek(char ch) { return _lexer.peek(ch); } private boolean peekIsId() { return _lexer.peekIsId(); } - private int pos() { return _lexer._position; } private int pos(int pos) { int old = _lexer._position; _lexer._position = pos; diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java index 4007df91..5becb4c1 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -9,8 +9,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java index 17a4e0ca..8ab44858 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -10,8 +10,6 @@ public class AndNode extends Node { @Override public String label() { return "And"; } - @Override public String glabel() { return "&"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 06c10a79..5d2212b0 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -21,9 +21,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java index 76e98678..d13dcacf 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -10,8 +10,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 1528f2e9..17ed127c 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 9727f58b..7cacf2c9 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 760a4274..50377061 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java index 949af27a..65898907 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java index ddb1e09a..77800be2 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -32,12 +32,8 @@ public class NewNode extends Node implements MultiNode { } @Override public String label() { - return "new_"+glabel(); + return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java index 08033df0..923c85c8 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; @@ -72,17 +74,11 @@ public abstract class Node implements OutNode { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -297,13 +293,14 @@ public abstract class Node implements OutNode { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if (_disablePeephole) { - _type = compute(); - return this; // Peephole optimizations turned off - } + public final Node peephole() { + if( _disablePeephole ) { _type = compute(); return this; } + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -461,6 +458,8 @@ public abstract class Node implements OutNode { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -471,6 +470,8 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java index 664d7c7b..cca84a2d 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java index b572346f..301514f4 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -10,8 +10,6 @@ public class OrNode extends Node { @Override public String label() { return "Or"; } - @Override public String glabel() { return "|"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 4962f4f4..0532d2b7 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -18,8 +18,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java index 5dc26ad3..25ef55e3 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -10,8 +10,6 @@ public class SarNode extends Node { @Override public String label() { return "Sar"; } - @Override public String glabel() { return ">>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 6fd951e4..1b2ed6ea 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -10,8 +10,6 @@ public class ShlNode extends Node { @Override public String label() { return "Shl"; } - @Override public String glabel() { return "<<"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 779193de..e60882d4 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -10,8 +10,6 @@ public class ShrNode extends Node { @Override public String label() { return "Shr"; } - @Override public String glabel() { return ">>>"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java index 59d38865..fb8c3cc4 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -4,7 +4,6 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.GlobalCodeMotion; -import com.seaofnodes.simple.GraphVisualizer; import java.util.BitSet; @@ -68,17 +67,15 @@ public class StopNode extends CFGNode { return addDef(node); } - public StopNode iterate( ) { return IterPeeps.iterate(this).typeCheck().GCM(false); } - public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); } + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } StopNode typeCheck() { String err = walk( Node::err ); if( err != null ) throw new RuntimeException(err); return this; } - StopNode GCM(boolean show) { + StopNode GCM() { Parser.START.buildLoopTree(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(this,null,null)); + GlobalCodeMotion.buildCFG(this); return this; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 62b49a1a..a5618960 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,10 +25,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java index 5d6c2643..24499cbc 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -11,8 +11,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1d147cf6..fde483d6 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java index 191933f9..71dce586 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -10,8 +10,6 @@ public class XorNode extends Node { @Override public String label() { return "Xor"; } - @Override public String glabel() { return "^"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..5a45aa9d --- /dev/null +++ b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,63 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 17's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..ab1d8c34 --- /dev/null +++ b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 17's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.add(_parser._scope); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index 88d79215..154b74a2 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -66,7 +66,7 @@ for( int pc = 0; pc < program#; pc++ ) { } return output; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("Hello World!\n", Evaluator.evaluate(stop, 0, 10000).toString()); } } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java index c5035381..089649bf 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -81,7 +81,7 @@ if( (arg/13)==0 ) { int r = g+h; return p-r; """); - StopNode stop = parser.parse(false).iterate(false); + StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } @@ -101,7 +101,7 @@ bar.a = 1; bar.a = 2; return bar.a; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); } @@ -117,7 +117,7 @@ else v.y = 3; return v; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Vector2D;", stop.toString()); } @@ -207,7 +207,7 @@ if (arg) bar = null; if( bar ) bar.a = 1; return bar; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region,(*void)Phi(Region,null,Bar),null);", stop.toString()); } @@ -223,7 +223,7 @@ if( !bar ) rez=4; else bar.a = 1; return rez; """); - StopNode stop = parser.parse(false).iterate(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region,4,3);", stop.toString()); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java index eeb28f1d..862b75e3 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -189,7 +189,7 @@ p2.pi.i = 2; if (arg) p1 = new P; return p1.pi.i + 1; """); - try { parser.parse(false).iterate(); fail(); } + try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } diff --git a/chapter18/Makefile b/chapter18/Makefile index c2e1b060..b1ec855b 100644 --- a/chapter18/Makefile +++ b/chapter18/Makefile @@ -37,6 +37,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -54,7 +55,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -77,10 +78,6 @@ fuzzer: $(default_targets) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap -# Launch viewer -view: $(main_classes) - @echo "viewing " - @$(JVM) com.seaofnodes.simple.JSViewer .PHONY: clean clean: @@ -105,4 +102,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter18/README.md b/chapter18/README.md index 0fa07818..1727c938 100644 --- a/chapter18/README.md +++ b/chapter18/README.md @@ -228,7 +228,7 @@ recursive calls). ## Graph Visualizer -Run as `make view` or `java JSViewer`, type your program in the text box, click +Run as `make view`, type your program in the text box, click outside the box and then use the arrow keys to view IR generator both forwards and backwards. diff --git a/chapter18/docs/18-grammar.md b/chapter18/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter18/docs/18-grammar.md +++ b/chapter18/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter18/pom.xml b/chapter18/pom.xml index 6b9b0d61..e7b4afaa 100644 --- a/chapter18/pom.xml +++ b/chapter18/pom.xml @@ -11,4 +11,7 @@ chapter18 jar Chapter 18 + + ${project.basedir}/../graph + diff --git a/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java b/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java index 1586b3c5..fb646544 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java @@ -1,10 +1,13 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.HashMap; public class CodeGen { + public GraphObserver _obs; // Inlining can insert CFG edges and invalidate cached depth ordering. private char _iDepthVersion; @@ -52,7 +55,7 @@ public class CodeGen { Node._disablePeephole = disable; _stop = P.parse(); _start = Parser.START; // Capture global start - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); P = null; // No longer parsing return this; } @@ -66,6 +69,7 @@ public class CodeGen { // TODO: // Optimistic + if( _obs != null ) _obs.phase("Opto"); return this; } @@ -84,15 +88,13 @@ public class CodeGen { } - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase == Phase.TypeCheck; _phase = Phase.Schedule; // Build the loop tree, fix never-exit loops _start.buildLoopTree(_stop); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + // TODO: // loop unroll, peel, RCE, etc diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 5d6d3328..00000000 --- a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - private static SB defPort(SB sb, Node def) { - return def instanceof MultiUse muse - ? sb.p(def.in(0).uniqueName()).p(":p").p(muse.idx()) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java index 24f0806a..1ed0c02a 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java @@ -147,8 +147,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } - public StopNode parse() { return parse(false); } - public StopNode parse(boolean show) { + public StopNode parse() { _xScopes.push(_scope); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); @@ -169,7 +168,6 @@ public class Parser { init.unkeep().kill(); INITS.clear(); STOP.peephole(); - if( show ) showGraph(); return STOP; } @@ -292,7 +290,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -593,14 +590,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java b/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java deleted file mode 100644 index 8a9123b4..00000000 --- a/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.seaofnodes.simple; - -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i= i2._max ) return FALSE; @@ -105,7 +101,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index e03bfb0e..83747871 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -32,8 +32,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } - @Override public String uniqueName() { return "Con_" + _nid; } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0d5ed297..69b151cd 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -9,8 +9,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java index eee5169d..54acc12c 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 2b749ba3..129963c2 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java index dd2cbd26..dad245fd 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java index a1544d3d..89e35cf2 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java index 34d695f4..241a3609 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -33,12 +33,8 @@ public class NewNode extends Node implements MultiNode { } @Override public String label() { - return "new_"+glabel(); + return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java index 02b95669..637f4b14 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.CodeGen; + import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; @@ -69,17 +71,11 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - - // ------------------------------------------------------------------------ // Debugger Printing. @@ -298,13 +294,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE == null || IterPeeps.midAssert() || CodeGen.CODE._phase == CodeGen.Phase.Parse && CodeGen.CODE.P == null ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -473,6 +469,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -483,6 +481,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; + var obs = CodeGen.CODE == null || IterPeeps.midAssert() || CodeGen.CODE._phase == CodeGen.Phase.Parse && CodeGen.CODE.P == null ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/NotNode.java index b454de66..5e631b78 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java index 9b03dc5d..5a87ce09 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -12,8 +12,6 @@ public class OrNode extends LogicalNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 784b9146..78609971 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -16,8 +16,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 4ca50a3c..b3e36d41 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -24,8 +24,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter18/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java index 4a71ec06..47091fed 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -11,8 +11,6 @@ public class SarNode extends LogicalNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 21dbd503..2342e7f5 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -11,8 +11,6 @@ public class ShlNode extends LogicalNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 789fb8a8..926bc993 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -11,8 +11,6 @@ public class ShrNode extends LogicalNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 0fa7d591..8f0725bb 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -26,10 +26,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java index 339c65ca..357bbc3b 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1f4d67e0..a4aa8934 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java index 37d7147d..ef022a2c 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -11,8 +11,6 @@ public class XorNode extends LogicalNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..23085c9c --- /dev/null +++ b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 18's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..975b289a --- /dev/null +++ b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 18's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @Override protected String phase() { return _code._phase.name(); } + @Override protected int pos() { return _code.P != null ? _code.P.pos() : -1; } + @Override protected void detach() { + if( _code != null ) _code._obs = null; + _code = null; + } + @Override protected void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code.P != null ) { + roots.add(com.seaofnodes.simple.Parser.START); + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter19/Makefile b/chapter19/Makefile index 3a6faaff..031c95c6 100644 --- a/chapter19/Makefile +++ b/chapter19/Makefile @@ -37,6 +37,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC)/$(SIMPLE)/node/cpus/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -54,7 +55,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java +$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -77,10 +78,6 @@ fuzzer: $(default_targets) @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap -# Launch viewer -view: $(main_classes) - @echo "viewing " - @$(JVM) com.seaofnodes.simple.JSViewer .PHONY: clean clean: @@ -105,4 +102,7 @@ tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) -include ../build-support/chapter-release.mk +include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) + +.PHONY: build +build: $(main_classes) diff --git a/chapter19/docs/18-grammar.md b/chapter19/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter19/docs/18-grammar.md +++ b/chapter19/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter19/pom.xml b/chapter19/pom.xml index d23cc4c6..a11923ca 100644 --- a/chapter19/pom.xml +++ b/chapter19/pom.xml @@ -11,4 +11,7 @@ chapter19 jar Chapter 19 + + ${project.basedir}/../graph + diff --git a/chapter19/src/main/java/com/seaofnodes/simple/CodeGen.java b/chapter19/src/main/java/com/seaofnodes/simple/CodeGen.java index e966200c..8465d692 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/CodeGen.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.cpus.x86_64_v2.MemOpX86; import com.seaofnodes.simple.type.*; @@ -8,6 +10,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; public class CodeGen { + public GraphObserver _obs; // Inlining can insert CFG edges and invalidate cached depth ordering. private char _iDepthVersion; @@ -123,7 +126,7 @@ public class CodeGen { _phase = Phase.Parse; P.parse(); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -151,6 +154,7 @@ public class CodeGen { // Optimistic // TODO: // loop unroll, peel, RCE, etc + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return (N)_iter.add(n); } @@ -250,16 +254,14 @@ public class CodeGen { Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.InstructionSelection.ordinal(); _phase = Phase.Schedule; // Build the loop tree, fix never-exit loops _start.buildLoopTree(_stop); GlobalCodeMotion.buildCFG(this); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter19/src/main/java/com/seaofnodes/simple/GraphVisualizer.java deleted file mode 100644 index 30e25916..00000000 --- a/chapter19/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java index 998bd4f0..1792dada 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -65,9 +65,14 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE == null ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -86,11 +91,11 @@ public abstract class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(stop); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java b/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java deleted file mode 100644 index 13ce4451..00000000 --- a/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java +++ /dev/null @@ -1,410 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.io.IOException; -import java.util.Stack; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - static CodeGen CODE; // - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CODE = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - CODE.parse(); - // No longer user parse internal state when building views - CODE.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - boolean midParse = CODE.P!=null; - // Skip util we at least get the Parse object made - if( !midParse && (CODE._phase==null || CODE._phase == CodeGen.Phase.Parse) ) - return; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) - if( use instanceof MultiUse muse ) { - cell(sb,use.glabel(),use,"p"+muse.idx()); - empty_row=false; - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._lexSize.at(level-1); - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java index 053b8c66..29738a31 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java @@ -130,8 +130,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } - public void parse() { parse(false); } - public void parse(boolean show) { + public void parse() { _xScopes.push(_scope); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); @@ -153,7 +152,6 @@ public class Parser { init.unkeep().kill(); INITS.clear(); _code._stop.peephole(); - if( show ) showGraph(); } /** @@ -275,7 +273,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -576,14 +573,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter19/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java b/chapter19/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java deleted file mode 100644 index 8a9123b4..00000000 --- a/chapter19/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.seaofnodes.simple; - -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i= i2._max ) return FALSE; @@ -104,7 +100,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -122,7 +117,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 46561644..98ce9a7f 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -35,8 +35,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } - @Override public String uniqueName() { return "Con_" + _nid; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 36fd4524..6c0c2eb3 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0d5ed297..69b151cd 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -9,8 +9,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 2978e0d0..c6c37d1a 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 2b749ba3..129963c2 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java index dd2cbd26..dad245fd 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java index 53472994..6dcbf456 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java index 80545d66..abce1601 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -33,11 +33,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java index 846aaa22..98e561db 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.CodeGen; + import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; @@ -75,16 +77,11 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" - public String glabel() { return label(); } - // Extra fun stuff, for assembly printing. Jump labels, parser locations, // variable types, etc. public String comment() { return null; } @@ -299,13 +296,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -467,6 +464,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -477,6 +476,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return this; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep == null ) return this; if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/NotNode.java index b454de66..5e631b78 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java index 9b03dc5d..5a87ce09 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -12,8 +12,6 @@ public class OrNode extends LogicalNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 8d7c4205..c7c21171 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -17,8 +17,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 10dfe648..add811d6 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -26,8 +26,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter19/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java index 4a71ec06..47091fed 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -11,8 +11,6 @@ public class SarNode extends LogicalNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 21dbd503..2342e7f5 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -11,8 +11,6 @@ public class ShlNode extends LogicalNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 789fb8a8..926bc993 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -11,8 +11,6 @@ public class ShrNode extends LogicalNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 93294dba..4e592235 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java index 339c65ca..357bbc3b 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1f4d67e0..a4aa8934 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java index 37d7147d..ef022a2c 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -11,8 +11,6 @@ public class XorNode extends LogicalNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..170dcb7d --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 19's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..b7f407fa --- /dev/null +++ b/chapter19/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; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 19's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter20/Makefile b/chapter20/Makefile index 9e33ae91..681b350e 100644 --- a/chapter20/Makefile +++ b/chapter20/Makefile @@ -36,6 +36,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC)/$(SIMPLE)/node/cpus/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) classes = $(main_classes) $(test_classes) @@ -53,7 +54,7 @@ endif default: $(default_targets) # Compile just the out-of-date files -$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java +$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java $(graph_javas) @echo "compiling " $@ " because " $? @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) @@ -88,10 +89,6 @@ build/release/simple.jar: $(main_classes) $(test_classes) @[ -d $(dir $@) ] || mkdir -p $(dir $@) @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -# Launch viewer -view: $(main_classes) - @echo "viewing " - @$(JVM) com.seaofnodes.simple.print.JSViewer .PHONY: clean spill-stats clean: @@ -115,3 +112,6 @@ lib/hamcrest-core-1.3.jar: tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) + +.PHONY: build +build: $(main_classes) diff --git a/chapter20/docs/18-grammar.md b/chapter20/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter20/docs/18-grammar.md +++ b/chapter20/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter20/pom.xml b/chapter20/pom.xml index 933bc689..efe00a9d 100644 --- a/chapter20/pom.xml +++ b/chapter20/pom.xml @@ -11,4 +11,7 @@ chapter20 jar Chapter 20 + + ${project.basedir}/../graph + diff --git a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java index 6208209f..42cfedba 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -64,9 +63,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -85,11 +89,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index 343a3d47..0809a6f3 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import java.text.ParseException; import java.util.*; @@ -132,8 +131,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } - public void parse() { parse(false); } - public void parse(boolean show) { + public void parse() { _xScopes.push(_scope); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); @@ -155,7 +153,6 @@ public class Parser { init.unkeep().kill(); INITS.clear(); _code._stop.peephole(); - if( show ) showGraph(); } /** @@ -277,7 +274,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -578,14 +574,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index b6efcf42..5dca3953 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; @@ -9,6 +11,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; public class CodeGen { + public GraphObserver _obs; // Inlining can insert CFG edges and invalidate cached depth ordering. private char _iDepthVersion; @@ -125,7 +128,7 @@ public class CodeGen { P.parse(); _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -156,6 +159,7 @@ public class CodeGen { // Optimistic // TODO: // loop unroll, peel, RCE, etc + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return (N)_iter.add(n); } @@ -261,8 +265,7 @@ public class CodeGen { public Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.InstSelect.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); @@ -271,8 +274,7 @@ public class CodeGen { _start.buildLoopTree(_stop); GlobalCodeMotion.buildCFG(this); _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 7d4e2910..5d0a473a 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java index bc65e79a..e1d16cf3 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -12,8 +12,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java index 3cfcbda3..b425a6ce 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -12,8 +12,6 @@ public class AndNode extends LogicalNode { @Override public String label() { return "And"; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 3f959e75..9faa5181 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -95,7 +92,6 @@ abstract public class BoolNode extends Node { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -108,7 +104,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -126,7 +121,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 73ba94ce..5ddf47a1 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -36,8 +36,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } - @Override public String uniqueName() { return "Con_" + _nid; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java index a6e459c9..258720c8 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0d5ed297..69b151cd 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -9,8 +9,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java index b3263541..df362850 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 2b749ba3..129963c2 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java index dd2cbd26..dad245fd 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 2d44a9fb..4b753ba7 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java index 80f8c319..c7fb620c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java index 80545d66..abce1601 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -33,11 +33,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java index f7cb1672..65dbbd76 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -78,14 +77,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, @@ -365,13 +362,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -533,6 +530,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -543,6 +542,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return this; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep == null ) return this; if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/NotNode.java index b454de66..5e631b78 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java index 9b03dc5d..5a87ce09 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -12,8 +12,6 @@ public class OrNode extends LogicalNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 8d7c4205..c7c21171 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -17,8 +17,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 34d7afc7..944e7ed6 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -26,8 +26,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter20/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 44d3a587..3dd7cbf0 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java index 4a71ec06..47091fed 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -11,8 +11,6 @@ public class SarNode extends LogicalNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 21dbd503..2342e7f5 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -11,8 +11,6 @@ public class ShlNode extends LogicalNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 789fb8a8..926bc993 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -11,8 +11,6 @@ public class ShrNode extends LogicalNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 93294dba..4e592235 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SubFNode.java index b591718d..27bbda8d 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java index 339c65ca..357bbc3b 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 1f4d67e0..a4aa8934 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java index 37d7147d..ef022a2c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -11,8 +11,6 @@ public class XorNode extends LogicalNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index e4cf2c31..00000000 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java index 265e0b21..087a990b 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -63,9 +63,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -84,11 +89,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/JSViewer.java b/chapter19/src/main/java/com/seaofnodes/simple/JSViewer.java deleted file mode 100644 index 98eca79f..00000000 --- a/chapter19/src/main/java/com/seaofnodes/simple/JSViewer.java +++ /dev/null @@ -1,418 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.io.IOException; -import java.util.Stack; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import static com.seaofnodes.simple.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._lexSize.at(level-1); - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..15afe5d1 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 20's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..5c284268 --- /dev/null +++ b/chapter20/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 20's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index e1b893f1..00000000 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; ichapter21 jar Chapter 21 + + ${project.basedir}/../graph + diff --git a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java index 66e8a4a8..ded3da7e 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -65,9 +64,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -86,11 +90,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java index 8e67ed10..adddbaa6 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import java.text.ParseException; import java.util.*; @@ -132,8 +131,7 @@ public class Parser { private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } - public void parse() { parse(false); } - public void parse(boolean show) { + public void parse() { _xScopes.push(_scope); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); @@ -173,7 +171,6 @@ public class Parser { init.unkeep().kill(); INITS.clear(); stop.peephole(); - if( show ) showGraph(); } /** @@ -295,7 +292,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -596,14 +592,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 029e149b..4bfd3c5d 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; @@ -11,6 +13,7 @@ import java.util.IdentityHashMap; @SuppressWarnings("unchecked") public class CodeGen { + public GraphObserver _obs; public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". @@ -148,7 +151,7 @@ public class CodeGen { P.parse(); _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -179,6 +182,7 @@ public class CodeGen { // Optimistic // TODO: // loop unroll, peel, RCE, etc + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return (N)_iter.add(n); } @@ -330,16 +334,14 @@ public class CodeGen { public Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.InstSelect.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 792da62e..ba5815da 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java index dd128e76..c8701b5d 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -12,8 +12,6 @@ public class AddNode extends Node { @Override public String label() { return "Add"; } - @Override public String glabel() { return "+"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java index 3cfcbda3..b425a6ce 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -12,8 +12,6 @@ public class AndNode extends LogicalNode { @Override public String label() { return "And"; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 5f96c591..b2753fb1 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -107,7 +104,6 @@ abstract public class BoolNode extends Node { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -120,7 +116,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -138,7 +133,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 3ffd430b..9785855b 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -36,8 +36,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } - @Override public String uniqueName() { return "Con_" + _nid; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 633f3224..2180ab46 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java index 41518311..7aa98bf2 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -9,8 +9,6 @@ public class DivNode extends Node { @Override public String label() { return "Div"; } - @Override public String glabel() { return "//"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 785ba62c..80bf989c 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 99fc3144..bfdb41cc 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java index e06e0c69..64f471a8 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 6fbf4bbb..0fc062cd 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java index 384dbace..7609f1d4 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -10,8 +10,6 @@ public class MulNode extends Node { @Override public String label() { return "Mul"; } - @Override public String glabel() { return "*"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java index 5a8c1b6b..27f7d634 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -35,11 +35,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java index a83ca6c1..1b543c69 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -78,14 +77,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, @@ -371,13 +368,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -540,6 +537,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -550,6 +549,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java index 0314841d..118ef5c8 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java index 9b03dc5d..5a87ce09 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -12,8 +12,6 @@ public class OrNode extends LogicalNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 9e11a129..0eb2e5f0 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -17,8 +17,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 24a766d6..8ab40af6 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -26,8 +26,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 7936086d..2ad034d6 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java index 4a71ec06..47091fed 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -11,8 +11,6 @@ public class SarNode extends LogicalNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 82e01841..ad7d9978 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -11,8 +11,6 @@ public class ShlNode extends LogicalNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 789fb8a8..926bc993 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -11,8 +11,6 @@ public class ShrNode extends LogicalNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java index a6992f04..8878193c 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java index 5c7e969f..73cf9de5 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java index 4290ff9b..51164dcc 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -10,8 +10,6 @@ public class SubNode extends Node { @Override public String label() { return "Sub"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index c098ad99..8ef5e40a 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java index 37d7147d..ef022a2c 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -11,8 +11,6 @@ public class XorNode extends LogicalNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override public Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index e4cf2c31..00000000 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index 30643c7a..00000000 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase == null ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._lexSize.at(level-1); - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..73967e05 --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 21's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..b04c58a2 --- /dev/null +++ b/chapter21/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 21's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index e1b893f1..00000000 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i "01-parse.dot"; - case DUMP_AFTER_OPTO -> "02-opto.dot"; - case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot"; - case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot"; - case DUMP_AFTER_INSTR_SELECT -> "05-instr_select.dot"; - case DUMP_AFTER_GCM -> "06-gcm.dot"; - case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot"; - case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot"; - case DUMP_AFTER_ENCODE -> "09-local_sched.dot"; - case DUMP_FINAL -> "10-final.dot"; + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_INSTR_SELECT -> "After Instruction Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Instruction Encoding:"; + case DUMP_FINAL -> "Final:"; default -> throw Utils.TODO(); - }; - - try { - Files.writeString(Path.of(fn), - new GraphVisualizer().generateDotOutput(code._stop, null, null)); - } catch(IOException e) { - System.err.println("ERROR: Cannot write DOT file"); - System.exit(1); - } - } else { - if ((dump & DUMP_PASS_NAME) != 0) { - System.err.println(switch (pass) { - case DUMP_AFTER_PARSE -> "After Parse:"; - case DUMP_AFTER_OPTO -> "After OPTO:"; - case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; - case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; - case DUMP_AFTER_INSTR_SELECT -> "After Instruction Selection:"; - case DUMP_AFTER_GCM -> "After GCM:"; - case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; - case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; - case DUMP_AFTER_ENCODE -> "After Instruction Encoding:"; - case DUMP_FINAL -> "Final:"; - default -> throw Utils.TODO(); - }); - } - - System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + }); } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + } } @@ -193,7 +167,6 @@ loop: for (int i = 0; i < args.length; i++) { case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; - case "--dot": dump |= DUMP_DOT; break; case "-S": dump |= DUMP_DISASSEMBLE; break; case "--eval": do_eval = true; first_arg = i + 1; break loop; case "--run": do_run = true; first_arg = i + 1; break loop; diff --git a/chapter22/Makefile b/chapter22/Makefile index 9d4f3d17..f2c05809 100644 --- a/chapter22/Makefile +++ b/chapter22/Makefile @@ -64,6 +64,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC)/$(SIMPLE)/node/cpus/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) com.seaofnodes.simple.codegen.X86EncodingTest classes = $(main_classes) $(test_classes) @@ -72,7 +73,7 @@ libs = $(wildcard lib/*jar) jars = $(subst $(space),$(SEP),$(libs)) -default_targets := $(CLZDIR)/.tag $(test_classes) +default_targets := $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag # Optionally add ctags to the default target if a reasonable one was found. ifneq ($(CTAGS),) default_targets += tags @@ -81,18 +82,17 @@ endif default: $(default_targets) ####################################################### -# Compile just the out-of-date files +# Compile the chapter and shared viewer sources together -OODM := -$(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) +$(CLZDIR)/main/.mtag: $(main_javas) Makefile @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @# This crap is really just "javac", but with a beautiful error message. @# The very very long list of java files is suppressed and counted. @# The required output "Note: blah blah deprecated blah" is suppressed. - $(file >.argsM.txt,$(OODM)) - @if [ ! -z "$(OODM)" ] ; then \ + $(file >.argsM.txt,$(main_javas)) + @if [ ! -z "$(main_javas)" ] ; then \ echo "compiling main because " $< " and " `wc -w < .argsM.txt` " more files" ; \ - if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(OODM) >& .out.txt ; then \ + if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) >& .out.txt ; then \ cat .out.txt ; \ exit 1; \ fi ; \ @@ -101,9 +101,8 @@ $(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) @touch $(CLZDIR)/main/.mtag @rm -f .argsM.txt -# Collect just the out-of-date files -$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java - $(eval OODM += $$<) +$(main_classes): $(CLZDIR)/main/.mtag + ####################################################### @@ -157,11 +156,6 @@ build/release/simple.jar: $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag @[ -d $(dir $@) ] || mkdir -p $(dir $@) @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -# Launch viewer -.PHONY: view -view: $(CLZDIR)/main/.mtag - @echo "viewing " - @$(JVM) com.seaofnodes.simple.print.JSViewer .PHONY: clean clean: @@ -185,3 +179,6 @@ lib/hamcrest-core-1.3.jar: tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) + +.PHONY: build +build: $(CLZDIR)/main/.mtag diff --git a/chapter22/docs/18-grammar.md b/chapter22/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter22/docs/18-grammar.md +++ b/chapter22/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter22/pom.xml b/chapter22/pom.xml index 4039d3ed..d06114f5 100644 --- a/chapter22/pom.xml +++ b/chapter22/pom.xml @@ -11,4 +11,7 @@ chapter22 jar Chapter 22 + + ${project.basedir}/../graph + diff --git a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java index 66e8a4a8..ded3da7e 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -65,9 +64,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -86,11 +90,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java index c26b9064..60a36574 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import java.util.*; @@ -303,7 +302,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -607,14 +605,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter22/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter22/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 1726695f..bdb9c5cc 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; @@ -11,6 +13,7 @@ import java.util.IdentityHashMap; @SuppressWarnings("unchecked") public class CodeGen { + public GraphObserver _obs; public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". @@ -150,7 +153,7 @@ public class CodeGen { P.parse(); _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -200,6 +203,7 @@ public class CodeGen { // Optimistic // TODO: // loop unroll, peel, RCE, etc + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return (N)_iter.add(n); } @@ -349,16 +353,14 @@ public class CodeGen { public Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.Select.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 792da62e..ba5815da 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/AndNode.java index 70b57d6d..9957472b 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -9,8 +9,6 @@ public class AndNode extends ArithNode { @Override public String label() { return "And"; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sharpen allowed bits if either value is narrowed diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 3e376418..f9fc2870 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -11,7 +11,6 @@ public abstract class ArithNode extends Node { Parser.Lexer _loc; public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } - @Override public String glabel() { return op(); } abstract String op(); @Override diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 0cf8b87e..80e8cf46 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -107,7 +104,6 @@ abstract public class BoolNode extends Node { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -120,7 +116,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -153,7 +148,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 3ffd430b..9785855b 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -36,8 +36,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } - @Override public String uniqueName() { return "Con_" + _nid; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 633f3224..2180ab46 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ExternNode.java index 33fc0c1e..a993d9de 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -16,7 +16,6 @@ public class ExternNode extends ConstantNode { public ExternNode(Type t, String ex) { super(t); _extern = ex; } @Override public String label() { return "#"+_con+":"+_extern; } - @Override public String glabel() { return _con.gprint(new SB().p("#")).p(":").p(_extern).toString(); } @Override public String uniqueName() { return "Extern_" + _nid; } @Override public boolean eq(Node n) { return this==n; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 10de5e53..4f09400a 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,11 +23,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 99fc3144..bfdb41cc 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 059d0d00..3a9eb39d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -11,8 +11,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 6fbf4bbb..0fc062cd 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java index 5a8c1b6b..27f7d634 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -35,11 +35,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java index eee5ad7e..be26fc2f 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -78,14 +77,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, @@ -375,13 +372,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -544,6 +541,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -554,6 +553,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/NotNode.java index 0314841d..118ef5c8 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java index ac0d8454..6b48815d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -9,9 +9,6 @@ public class OrNode extends ArithNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - - @Override long doOp( long x, long y ) { return x | y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 32e41f54..f75e7c64 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -17,8 +17,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java index f0845066..f95a452d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -26,8 +26,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter22/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 7936086d..2ad034d6 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java index ee15fa65..6b8969e3 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -9,8 +9,6 @@ public class SarNode extends ArithNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( y.isConstant() ) { diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 2557bf6b..60c91fc9 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -9,8 +9,6 @@ public class ShlNode extends ArithNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( x == TypeInteger.ZERO ) diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 269f6b7f..81dae615 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -9,8 +9,6 @@ public class ShrNode extends ArithNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return x == TypeInteger.ZERO ? x : TypeInteger.BOT; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 9b561bed..ce989826 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/SubFNode.java index 5c7e969f..73cf9de5 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index c098ad99..8ef5e40a 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java index fdb4863e..cff1706a 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -9,8 +9,6 @@ public class XorNode extends ArithNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return TypeInteger.BOT; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter22/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index e4cf2c31..00000000 --- a/chapter22/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index c1a1d7e2..00000000 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._lexSize.at(level-1); - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..8dcee566 --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 22's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..584a16fe --- /dev/null +++ b/chapter22/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 22's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index e1b893f1..00000000 --- a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i "01-parse.dot"; - case DUMP_AFTER_OPTO -> "02-opto.dot"; - case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot"; - case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot"; - case DUMP_AFTER_SELECT -> "05-instr_select.dot"; - case DUMP_AFTER_GCM -> "06-gcm.dot"; - case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot"; - case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot"; - case DUMP_AFTER_ENCODE -> "09-local_sched.dot"; - case DUMP_FINAL -> "10-final.dot"; - default -> throw Utils.TODO(); - }; - - try { - Files.writeString(Path.of(fn), - new GraphVisualizer().generateDotOutput(code._stop, null, null)); - } catch(IOException e) { throw bad("Cannot write DOT file"); } - } else { - if ((dump & DUMP_PASS_NAME) != 0) { - System.err.println(switch (pass) { - case DUMP_AFTER_PARSE -> "After Parse:"; - case DUMP_AFTER_OPTO -> "After OPTO:"; - case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; - case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; - case DUMP_AFTER_SELECT -> "After Code Selection:"; - case DUMP_AFTER_GCM -> "After GCM:"; - case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; - case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; - case DUMP_AFTER_ENCODE -> "After Encoding:"; - case DUMP_FINAL -> "Final:"; - default -> throw Utils.TODO(); - }); - } - - System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_SELECT -> "After Code Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Encoding:"; + case DUMP_FINAL -> "Final:"; + default -> throw Utils.TODO(); + }); } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + } } @@ -183,7 +160,6 @@ Options: case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; - case "--dot": dump |= DUMP_DOT; break; case "-S": dump |= DUMP_DISASSEMBLE; break; case "--eval": do_eval = true ; do_run = false; break; case "--run": do_run = true ; break; diff --git a/chapter23/Makefile b/chapter23/Makefile index 9d4f3d17..f2c05809 100644 --- a/chapter23/Makefile +++ b/chapter23/Makefile @@ -64,6 +64,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC)/$(SIMPLE)/node/cpus/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) com.seaofnodes.simple.codegen.X86EncodingTest classes = $(main_classes) $(test_classes) @@ -72,7 +73,7 @@ libs = $(wildcard lib/*jar) jars = $(subst $(space),$(SEP),$(libs)) -default_targets := $(CLZDIR)/.tag $(test_classes) +default_targets := $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag # Optionally add ctags to the default target if a reasonable one was found. ifneq ($(CTAGS),) default_targets += tags @@ -81,18 +82,17 @@ endif default: $(default_targets) ####################################################### -# Compile just the out-of-date files +# Compile the chapter and shared viewer sources together -OODM := -$(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) +$(CLZDIR)/main/.mtag: $(main_javas) Makefile @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @# This crap is really just "javac", but with a beautiful error message. @# The very very long list of java files is suppressed and counted. @# The required output "Note: blah blah deprecated blah" is suppressed. - $(file >.argsM.txt,$(OODM)) - @if [ ! -z "$(OODM)" ] ; then \ + $(file >.argsM.txt,$(main_javas)) + @if [ ! -z "$(main_javas)" ] ; then \ echo "compiling main because " $< " and " `wc -w < .argsM.txt` " more files" ; \ - if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(OODM) >& .out.txt ; then \ + if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) >& .out.txt ; then \ cat .out.txt ; \ exit 1; \ fi ; \ @@ -101,9 +101,8 @@ $(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) @touch $(CLZDIR)/main/.mtag @rm -f .argsM.txt -# Collect just the out-of-date files -$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java - $(eval OODM += $$<) +$(main_classes): $(CLZDIR)/main/.mtag + ####################################################### @@ -157,11 +156,6 @@ build/release/simple.jar: $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag @[ -d $(dir $@) ] || mkdir -p $(dir $@) @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -# Launch viewer -.PHONY: view -view: $(CLZDIR)/main/.mtag - @echo "viewing " - @$(JVM) com.seaofnodes.simple.print.JSViewer .PHONY: clean clean: @@ -185,3 +179,6 @@ lib/hamcrest-core-1.3.jar: tags: $(main_javas) $(test_javas) @rm -f TAGS @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) + +.PHONY: build +build: $(CLZDIR)/main/.mtag diff --git a/chapter23/docs/18-grammar.md b/chapter23/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter23/docs/18-grammar.md +++ b/chapter23/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter23/pom.xml b/chapter23/pom.xml index 6a59188f..7a2b2b78 100644 --- a/chapter23/pom.xml +++ b/chapter23/pom.xml @@ -11,4 +11,7 @@ chapter23 jar Chapter 23 + + ${project.basedir}/../graph + diff --git a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java index 9ae4231e..c873e74c 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -66,9 +65,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -88,11 +92,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java index beea1dcf..41d8fcb5 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; @@ -317,7 +316,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -635,14 +633,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 02ade2c1..ca0e1110 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; @@ -12,6 +14,7 @@ import java.util.*; @SuppressWarnings("unchecked") public class CodeGen { + public GraphObserver _obs; public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". @@ -152,7 +155,7 @@ public class CodeGen { P.parse(); _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -222,6 +225,7 @@ public class CodeGen { // Optimistic // TODO: // loop unroll, peel, RCE, etc + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return _iter.add(n); } @@ -371,16 +375,14 @@ public class CodeGen { public Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.Select.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/AddFNode.java index 792da62e..ba5815da 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -10,8 +10,6 @@ public class AddFNode extends Node { @Override public String label() { return "AddF"; } - @Override public String glabel() { return "+"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/AndNode.java index 70b57d6d..9957472b 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -9,8 +9,6 @@ public class AndNode extends ArithNode { @Override public String label() { return "And"; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sharpen allowed bits if either value is narrowed diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 96067c8e..2be8e4a2 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -11,7 +11,6 @@ public abstract class ArithNode extends Node { Parser.Lexer _loc; public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } - @Override public String glabel() { return op(); } abstract String op(); @Override diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 513f8d31..1a837435 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -107,7 +104,6 @@ abstract public class BoolNode extends Node { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -120,7 +116,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -153,7 +148,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index 3e1d3c69..234b37d9 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -21,7 +21,6 @@ public class ConFldOffNode extends ConstantNode { @Override public String label() { return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; } - @Override public String glabel() { return label(); } @Override public String uniqueName() { return "Off_" + _nid; } @Override diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 15d98a67..8fd0e61e 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -31,7 +31,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return "#"+_con.gprint(); } @Override public String uniqueName() { return "Con_" + _nid; } @Override diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 633f3224..2180ab46 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -10,8 +10,6 @@ public class DivFNode extends Node { @Override public String label() { return "DivF"; } - @Override public String glabel() { return "/"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ExternNode.java index a10bb84a..21ef8eff 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -16,7 +16,6 @@ public class ExternNode extends ConstantNode { public ExternNode(Type t, String ex) { super(t); _extern = ex; } @Override public String label() { return "#"+_con+":"+_extern; } - @Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); } @Override public String uniqueName() { return "Extern_" + _nid; } @Override public boolean eq(Node n) { return this==n; } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java index ec6260c0..94b10027 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -24,11 +24,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 99fc3144..bfdb41cc 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java index f26ce37e..a0550609 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MulFNode.java index 6fbf4bbb..0fc062cd 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -10,8 +10,6 @@ public class MulFNode extends Node { @Override public String label() { return "MulF"; } - @Override public String glabel() { return "*"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java index 1785af4e..b0b4103b 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -34,11 +34,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java index 715f4e14..ab221789 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java @@ -5,7 +5,6 @@ import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -80,14 +79,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, @@ -378,13 +375,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -547,6 +544,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if its not an input @@ -557,6 +556,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/NotNode.java index 4b80e61c..1cbd2402 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java index ac0d8454..6b48815d 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -9,9 +9,6 @@ public class OrNode extends ArithNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - - @Override long doOp( long x, long y ) { return x | y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ParmNode.java index a14a4fb9..89465d45 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -16,8 +16,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java index c86b3345..a711fe81 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -35,8 +35,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter23/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 7936086d..2ad034d6 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java index ee15fa65..6b8969e3 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -9,8 +9,6 @@ public class SarNode extends ArithNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( y.isConstant() ) { diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 2557bf6b..60c91fc9 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -9,8 +9,6 @@ public class ShlNode extends ArithNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( x == TypeInteger.ZERO ) diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 269f6b7f..81dae615 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -9,8 +9,6 @@ public class ShrNode extends ArithNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return x == TypeInteger.ZERO ? x : TypeInteger.BOT; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 5fb274db..9c4bb4ef 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/SubFNode.java index 5c7e969f..73cf9de5 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -10,8 +10,6 @@ public class SubFNode extends Node { @Override public String label() { return "SubF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index c098ad99..8ef5e40a 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java index fdb4863e..cff1706a 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -9,8 +9,6 @@ public class XorNode extends ArithNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return TypeInteger.BOT; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter23/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index 20c8439a..00000000 --- a/chapter23/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart=scope._lexSize.at(level); - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter22/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index c1a1d7e2..00000000 --- a/chapter22/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.SB; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope._lexSize.size()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._lexSize.at(level-1); - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..38a4778f --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 23's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..6e421c2c --- /dev/null +++ b/chapter23/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 23's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index d7fa8bf2..00000000 --- a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.util.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i "01-parse.dot"; - case DUMP_AFTER_OPTO -> "02-opto.dot"; - case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot"; - case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot"; - case DUMP_AFTER_SELECT -> "05-instr_select.dot"; - case DUMP_AFTER_GCM -> "06-gcm.dot"; - case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot"; - case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot"; - case DUMP_AFTER_ENCODE -> "09-local_sched.dot"; - case DUMP_FINAL -> "10-final.dot"; - default -> throw Utils.TODO(); - }; - - try { - Files.writeString(Path.of(fn), - new GraphVisualizer().generateDotOutput(code._stop, null, null)); - } catch(IOException e) { throw bad("Cannot write DOT file"); } - } else { - if ((dump & DUMP_PASS_NAME) != 0) { - System.err.println(switch (pass) { - case DUMP_AFTER_PARSE -> "After Parse:"; - case DUMP_AFTER_OPTO -> "After OPTO:"; - case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; - case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; - case DUMP_AFTER_SELECT -> "After Code Selection:"; - case DUMP_AFTER_GCM -> "After GCM:"; - case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; - case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; - case DUMP_AFTER_ENCODE -> "After Encoding:"; - case DUMP_FINAL -> "Final:"; - default -> throw Utils.TODO(); - }); - } - - System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_SELECT -> "After Code Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Encoding:"; + case DUMP_FINAL -> "Final:"; + default -> throw Utils.TODO(); + }); } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + } } @@ -183,7 +160,6 @@ Options: case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; - case "--dot": dump |= DUMP_DOT; break; case "-S": dump |= DUMP_DISASSEMBLE; break; case "--eval": do_eval = true ; do_run = false; break; case "--run": do_run = true ; break; diff --git a/chapter24/Makefile b/chapter24/Makefile index 42ba8a03..94605e84 100644 --- a/chapter24/Makefile +++ b/chapter24/Makefile @@ -64,6 +64,7 @@ CLZDIR:= build/classes main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC)/$(SIMPLE)/node/cpus/*/*java) test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) com.seaofnodes.simple.codegen.X86EncodingTest classes = $(main_classes) $(test_classes) @@ -72,7 +73,7 @@ libs = $(wildcard lib/*jar) jars = $(subst $(space),$(SEP),$(libs)) -default_targets := $(CLZDIR)/.tag $(test_classes) +default_targets := $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag # Optionally add ctags to the default target if a reasonable one was found. ifneq ($(CTAGS),) default_targets += tags @@ -81,18 +82,17 @@ endif default: $(default_targets) ####################################################### -# Compile just the out-of-date files +# Compile the chapter and shared viewer sources together -OODM := -$(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) +$(CLZDIR)/main/.mtag: $(main_javas) Makefile @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main @# This crap is really just "javac", but with a beautiful error message. @# The very very long list of java files is suppressed and counted. @# The required output "Note: blah blah deprecated blah" is suppressed. - $(file >.argsM.txt,$(OODM)) - @if [ ! -z "$(OODM)" ] ; then \ + $(file >.argsM.txt,$(main_javas)) + @if [ ! -z "$(main_javas)" ] ; then \ echo "compiling main because " $< " and " `wc -w < .argsM.txt` " more files" ; \ - if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(OODM) >& .out.txt ; then \ + if ! javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) >& .out.txt ; then \ cat .out.txt ; \ exit 1; \ fi ; \ @@ -101,9 +101,8 @@ $(CLZDIR)/main/.mtag: $(main_classes) $(main_javas) @touch $(CLZDIR)/main/.mtag @rm -f .argsM.txt -# Collect just the out-of-date files -$(main_classes): $(CLZDIR)/main/%class: $(SRC)/%java - $(eval OODM += $$<) +$(main_classes): $(CLZDIR)/main/.mtag + ####################################################### @@ -151,11 +150,6 @@ build/release/simple.jar: $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag @[ -d $(dir $@) ] || mkdir -p $(dir $@) @jar cf build/release/simple.jar -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -# Launch viewer -.PHONY: view -view: $(CLZDIR)/main/.mtag - @echo "viewing " - @$(JVM) com.seaofnodes.simple.print.JSViewer .PHONY: clean clean: @@ -185,3 +179,6 @@ spill-stats: $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag @$(JVM) com.seaofnodes.simple.SpillStats .PHONY: spill-stats + +.PHONY: build +build: $(CLZDIR)/main/.mtag diff --git a/chapter24/docs/18-grammar.md b/chapter24/docs/18-grammar.md index 7b611fbf..7da725e9 100644 --- a/chapter24/docs/18-grammar.md +++ b/chapter24/docs/18-grammar.md @@ -15,7 +15,6 @@ statement | whileStatement | breakStatement | continueStatement - | metaStatement | structDeclaration | expressionStatement ; @@ -31,7 +30,6 @@ breakStatement : 'break' ';' ; continueStatement : 'continue' ';' ; -metaStatement : '#showGraph' ';' ; structDeclaration : 'struct' IDENTIFIER '{' block '}' ; diff --git a/chapter24/pom.xml b/chapter24/pom.xml index 4f0f80b1..c487fbff 100644 --- a/chapter24/pom.xml +++ b/chapter24/pom.xml @@ -11,4 +11,7 @@ chapter24 jar Chapter 24 + + ${project.basedir}/../graph + diff --git a/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java index 18ff470a..c924ccea 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -5,7 +5,6 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.Random; @@ -68,9 +67,14 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { - if( x.isDead() ) continue; + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -90,11 +94,11 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again assert progressOnList(code, _work, true); // Very expensive assert } if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead + if( obs != null ) obs.after(n, x, true); } } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java index 1932772c..c9e53b4b 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; @@ -325,7 +324,6 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -642,14 +640,7 @@ public class Parser { return expr; } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); - return null; - } + /** Parse: [name '='] expr */ diff --git a/chapter24/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter24/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 3c6e825d..d4af3a10 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; @@ -13,6 +15,7 @@ import java.util.*; @SuppressWarnings("unchecked") public class CodeGen { + public GraphObserver _obs; public static final String PORTS = "com.seaofnodes.simple.node.cpus"; // Last created CodeGen as a global; used all over to avoid passing about a // "context". @@ -165,7 +168,7 @@ public class CodeGen { P.parse(); _tParse = (int)(System.currentTimeMillis() - t0); - JSViewer.show(); + if( _obs != null ) _obs.phase("Parse"); return this; } @@ -191,6 +194,7 @@ public class CodeGen { Opto.opto(this); _tOpto = (int)(System.currentTimeMillis() - t0); + if( _obs != null ) _obs.phase("Opto"); return this; } public N add( N n ) { return _iter.add(n); } @@ -339,16 +343,14 @@ public class CodeGen { public Ary _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.Select.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); _tGCM = (int)(System.currentTimeMillis() - t0); - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/AndNode.java index 70b57d6d..9957472b 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -9,8 +9,6 @@ public class AndNode extends ArithNode { @Override public String label() { return "And"; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sharpen allowed bits if either value is narrowed diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java index f2beaacb..6c147fe8 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -11,7 +11,6 @@ public abstract class ArithNode extends Node { Parser.Lexer _loc; public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } - @Override public String glabel() { return op(); } abstract String op(); @Override diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 513f8d31..1a837435 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -17,9 +17,6 @@ abstract public class BoolNode extends Node { @Override public String label() { return getClass().getSimpleName(); } - @Override - public String glabel() { return op(); } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -107,7 +104,6 @@ abstract public class BoolNode extends Node { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -120,7 +116,6 @@ abstract public class BoolNode extends Node { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -153,7 +148,6 @@ abstract public class BoolNode extends Node { public static class ULT extends BoolNode { public ULT(Node lhs, Node rhs) { super(lhs,rhs); } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index c54da2c4..56c125e2 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -21,7 +21,6 @@ public class ConFldOffNode extends ConstantNode { @Override public String label() { return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; } - @Override public String glabel() { return label(); } @Override public String uniqueName() { return "Off_" + _nid; } @Override diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index d5d70a0f..89289cfc 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -31,7 +31,6 @@ public class ConstantNode extends Node { } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return "#"+_con.gprint(); } @Override public String uniqueName() { return "Con_" + _nid; } @Override diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ExternNode.java index a10bb84a..21ef8eff 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -16,7 +16,6 @@ public class ExternNode extends ConstantNode { public ExternNode(Type t, String ex) { super(t); _extern = ex; } @Override public String label() { return "#"+_con+":"+_extern; } - @Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); } @Override public String uniqueName() { return "Extern_" + _nid; } @Override public boolean eq(Node n) { return this==n; } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 10fa9d43..b32484c2 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -24,11 +24,8 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 5bb0889f..aa66a7f0 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -10,8 +10,6 @@ public class MinusFNode extends Node { @Override public String label() { return "MinusF"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 3ab74625..e87d8dd4 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -10,8 +10,6 @@ public class MinusNode extends Node { @Override public String label() { return "Minus"; } - @Override public String glabel() { return "-"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java index 9310aa38..1a15439e 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -35,11 +35,7 @@ public class NewNode extends Node implements MultiNode { public Node mem (int idx) { return in(idx+2); } - @Override public String label() { return "new_"+glabel(); } - @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str(); - } - + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java index f45a1aa8..5583d3d6 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeInteger; @@ -81,14 +80,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, @@ -379,13 +376,13 @@ public abstract class Node implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -548,6 +545,8 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if it's not an input @@ -558,6 +557,8 @@ public abstract class Node implements Cloneable { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/NotNode.java index 4b80e61c..1cbd2402 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,8 +9,6 @@ public class NotNode extends Node { @Override public String label() { return "Not"; } - @Override public String glabel() { return "!"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java index ac0d8454..6b48815d 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -9,9 +9,6 @@ public class OrNode extends ArithNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - - @Override long doOp( long x, long y ) { return x | y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 880f936c..48d05e6b 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -18,8 +18,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - public FunNode fun() { return (FunNode)in(0); } @Override diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java index b2483218..81af208c 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -37,8 +37,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter24/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 7936086d..2ad034d6 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -10,8 +10,6 @@ public class RoundF32Node extends Node { @Override public String label() { return "RoundF32"; } - @Override public String glabel() { return "(f32)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java index ee15fa65..6b8969e3 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -9,8 +9,6 @@ public class SarNode extends ArithNode { @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( y.isConstant() ) { diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 2557bf6b..60c91fc9 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -9,8 +9,6 @@ public class ShlNode extends ArithNode { @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( x == TypeInteger.ZERO ) diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 269f6b7f..81dae615 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -9,8 +9,6 @@ public class ShrNode extends ArithNode { @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return x == TypeInteger.ZERO ? x : TypeInteger.BOT; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 8e33512a..09257730 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -24,10 +24,8 @@ public class StoreNode extends MemOpNode { _init = init; } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node val() { return in(4); } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index e02183a0..5734bcbc 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -11,8 +11,6 @@ public class ToFloatNode extends Node { @Override public String label() { return "ToFloat"; } - @Override public String glabel() { return "(flt)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java index fdb4863e..cff1706a 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -9,8 +9,6 @@ public class XorNode extends ArithNode { @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return TypeInteger.BOT; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter24/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index 20c8439a..00000000 --- a/chapter24/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(stop._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart = scope._kinds.at(level)._lexSize; - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter23/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index f4f72a3b..00000000 --- a/chapter23/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize; - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - private static SB node(SB sb, String text, Node n) { - String color = color(n); - sb.p("style=filled fillcolor=").p(color).p(" "); - String shape = switch( n ) { - case FunNode fun -> "shape=box peripheries=2"; - case CFGNode cfg -> "shape=box"; - default -> ""; - }; - sb.p(shape).p(" "); - return colorcell(sb.p("label=<"),text,n,dark(color)).p(">"); - } - - private static String color(Node n) { - return switch( n ) { - case CFGNode cfg -> "yellow"; - case NewNode nnn -> "lightgreen"; - case StructNode s -> "lightgreen"; - default -> typeColor(n._type); - }; - } - private static String typeColor(Type t) { - return t==null ? "white" : switch( t ) { - case TypeMem mem -> "blue"; - case TypeMemPtr tmp -> "green"; - case TypeFunPtr tfp -> "orange"; - case TypeRPC rpc -> "antiquewhite"; - case TypeInteger ti -> "lightblue"; - case TypeFloat tf -> "aqua"; - case Type tn -> tn==Type.NIL ? "lightgray" : "red"; - }; - } - private static final String[] DARKS = new String[]{"blue","green","black"}; - private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; } - - - - // Called with an open div - private static SB colorcell(SB sb, String text, Node n, boolean dark) { - if( dark ) sb.p(""); - sb.p(text); - if( n._type!=null ) { - sb.p("
    "); - n._type.gprint(sb); - sb.p(""); - } - if( dark ) sb.p("
    "); - return sb; - } - - - private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private static void nodeEdges(SB sb, ScopeNode scope, Collection all) { - // All them edge labels - String scopeName = scope!=null ? makeScopeName(scope, 0) : null; - sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - if( n instanceof ConstantNode || - n instanceof XCtrlNode || - // ProjNodes handled by Multi; - n instanceof ProjNode || - n instanceof CProjNode || - // ScopeNodes are done separately - n instanceof ScopeNode || - n instanceof MemMergeNode - ) - continue; - if( n.isDead() ) - throw Utils.TODO(); - // Normal edges - for( int i=0; i "); - if( n instanceof PhiNode && def instanceof RegionNode ) { - // Draw a dotted use->def edge from Phi to Region. - sb.p(def.uniqueName()); - sb.p(" [style=dotted taillabel=").p(i).p("];\n"); - } else { - // Most edges land here use->def - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.p(mname).p(":p").p(proj._idx); - } else sb.p(def.uniqueName()); - // Number edges, so we can see how they track - sb.p("[taillabel=").p(i); - // Color the edge - nodeEdgeColor(sb,def._type); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.p(" constraint=false"); - sb.p("];\n"); - } - } - - // Bonus edge if hooked by parser - if( (n.iskeep() || n.isUnused()) && scopeName != null ) { - sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); - } - // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) - sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); - } - } - - private static void nodeEdgeColor(SB sb, Type t) { - sb.p(" color=").p(typeColor(t)); - } - - // Walk the scope edges - private static void scopeEdges( SB sb, ScopeNode scope ) { - sb.i().p("edge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - if( v._idx==1 ) continue; // Memory done separately - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level); - sb.i() - .p(scopeName).p(":") - .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted - .p(" -> "); - defPort(sb,def).p(";\n"); - } - - // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); - for( int i=2; i "); - defPort(sb,def).p(";\n"); - } - } - - } - - // (C)Projection index or -1 - private static int idx(Node n) { - return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1); - } - - private static SB defPort(SB sb, Node def) { - int idx = idx(def); - return idx != -1 - ? sb.p(def.in(0).uniqueName()).p(":p").p(idx) - : sb.p(def.uniqueName()); - } -} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..bf8f4181 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 24's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList edges(Node n) { + var edges = new ArrayList(); + for( int i = 0; i < n.nIns(); i++ ) { + String name = n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) + : n instanceof CProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode || 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 ProjNode || n instanceof CProjNode ) + return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + 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) ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..ee540508 --- /dev/null +++ b/chapter24/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 24's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } + @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 void roots(ArrayList roots) { + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index d7fa8bf2..00000000 --- a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.util.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i 65535 ) - throw Utils.TODO("MOAR LENGTH"); - _out.write(129); // FIN, opcode 1 - whole text message - _out.write(126); // Length is 16b - _out.write(len>>>8); - _out.write(len&255); - for( int i=0; i "01-parse.dot"; - case DUMP_AFTER_OPTO -> "02-opto.dot"; - case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot"; - case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot"; - case DUMP_AFTER_SELECT -> "05-instr_select.dot"; - case DUMP_AFTER_GCM -> "06-gcm.dot"; - case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot"; - case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot"; - case DUMP_AFTER_ENCODE -> "09-local_sched.dot"; - case DUMP_FINAL -> "10-final.dot"; - default -> throw Utils.TODO(); - }; - - try { - Files.writeString(Path.of(fn), - new GraphVisualizer().generateDotOutput(code._stop, null, null)); - } catch(IOException e) { throw bad("Cannot write DOT file"); } - } else { - if ((dump & DUMP_PASS_NAME) != 0) { - System.err.println(switch (pass) { - case DUMP_AFTER_PARSE -> "After Parse:"; - case DUMP_AFTER_OPTO -> "After OPTO:"; - case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; - case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; - case DUMP_AFTER_SELECT -> "After Code Selection:"; - case DUMP_AFTER_GCM -> "After GCM:"; - case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; - case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; - case DUMP_AFTER_ENCODE -> "After Encoding:"; - case DUMP_FINAL -> "Final:"; - default -> throw Utils.TODO(); - }); - } - - System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + if ((dump & DUMP_PASS_NAME) != 0) { + System.err.println(switch (pass) { + case DUMP_AFTER_PARSE -> "After Parse:"; + case DUMP_AFTER_OPTO -> "After OPTO:"; + case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; + case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; + case DUMP_AFTER_SELECT -> "After Code Selection:"; + case DUMP_AFTER_GCM -> "After GCM:"; + case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; + case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; + case DUMP_AFTER_ENCODE -> "After Encoding:"; + case DUMP_FINAL -> "Final:"; + default -> throw Utils.TODO(); + }); } + + System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); + } } @@ -183,7 +160,6 @@ Options: case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; - case "--dot": dump |= DUMP_DOT; break; case "-S": dump |= DUMP_DISASSEMBLE; break; case "--eval": do_eval = true ; do_run = false; break; case "--run": do_run = true ; break; diff --git a/chapter25/Makefile b/chapter25/Makefile index 886354d3..fb71fc6b 100644 --- a/chapter25/Makefile +++ b/chapter25/Makefile @@ -65,10 +65,7 @@ main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java $(SRC test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) simple_driver := $(TST)/$(SIMPLE)/Simple.java main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) -# Compile the common adapter into this chapter without copying its sources. -GRAPH_DIR := $(firstword $(wildcard ../graph graph)) -graph_javas := $(wildcard $(GRAPH_DIR)/src/main/java/com/seaofnodes/graph/*.java) -main_javas += $(graph_javas) +include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) # These tests do not rely on the stdlib test_alone := com.seaofnodes.simple.BrainFuckTest com.seaofnodes.simple.MergeSortTest com.seaofnodes.simple.TypeTest @@ -218,11 +215,6 @@ build/release/Simple.jar: $(CLZDIR)/main/.mtag $(CLZDIR)/test/.ttag @printf "Main-Class: com.seaofnodes.simple.Simple\nClass-Path: ../../lib/junit-4.12.jar ../../lib/hamcrest-core-1.3.jar\n\n" > build/release/MANIFEST.MF @jar cfm $@ build/release/MANIFEST.MF -C $(CLZDIR)/main . -C $(CLZDIR)/test . -C $(SRC)/$(SIMPLE) . -C $(TST)/$(SIMPLE) . -# Launch viewer -.PHONY: view -view: $(CLZDIR)/main/.mtag - @echo "viewing " - @$(JVM) com.seaofnodes.simple.print.JSViewer ####################################################### .PHONY: clean diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index f8674d64..87aa96c4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -275,14 +275,7 @@ public class CodeGen { private int dump(int dump) { int p2 = _phase.ordinal(); if( (dump & (1< _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes - public CodeGen GCM() { return GCM(false); } - public CodeGen GCM( boolean show) { + public CodeGen GCM() { assert _phase.ordinal() <= Phase.Select.ordinal(); _phase = Phase.Schedule; long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0; - if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(compunit(),null,null)); + return this; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java index 95095ac3..f02007bc 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -9,8 +9,6 @@ public class AndNode extends ArithNode { @Override public Tag serialTag() { return Tag.And; } @Override public String op() { return "&"; } - @Override public String glabel() { return "&"; } - @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sharpen allowed bits if either value is narrowed diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java index dfd81504..60f00854 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -21,7 +21,6 @@ public abstract class ArithNode extends Node implements ModeNode { @Override public void packed(BAOS baos, HashMap strs, HashMap types, IdentityHashMap anodes) { baos.packed1(_mode); } - @Override public String glabel() { return op(); } abstract String op(); @Override diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java index ac3b0bc0..f720d02d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -23,9 +23,6 @@ abstract public class BoolNode extends Node implements ModeNode { abstract public String op(); // String opcode name - @Override - public String glabel() { return op(); } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("("), visited); @@ -154,7 +151,6 @@ abstract public class BoolNode extends Node implements ModeNode { public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } @Override public Tag serialTag() { return Tag.LT; } public String op() { return "<" ; } - public String glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -169,7 +165,6 @@ abstract public class BoolNode extends Node implements ModeNode { public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } @Override public Tag serialTag() { return Tag.LE; } public String op() { return "<="; } - public String glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -188,7 +183,6 @@ abstract public class BoolNode extends Node implements ModeNode { } @Override public Tag serialTag() { return Tag.ULT; } public String op() { return "u<" ; } - public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index 11a16d28..c7ee1359 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -25,7 +25,6 @@ public class ConFldOffNode extends ConstantNode { @Override public String label() { return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname; } - @Override public String glabel() { return label(); } @Override public String uniqueName() { return "Off_" + _nid; } @Override diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index df2dc27b..1dc369d3 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -67,7 +67,6 @@ public class ConstantNode extends TypeNode { public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return "#"+_con.gprint(); } @Override public String uniqueName() { return "Con_" + _nid; } @Override public Node copy() { return raw(this); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java index 9768f41f..58d96745 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -29,7 +29,6 @@ public class ExternNode extends ConstantNode { } @Override public String label() { return "#"+_con+":"+_extern; } - @Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); } @Override public String uniqueName() { return "Extern_" + _nid; } @Override public boolean eq(Node n) { return this==n; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java index 3a44f588..e34799c9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java @@ -23,7 +23,6 @@ public class FldOffNode extends TypeNode { @Override public String label() { return _fname == " len" ? "sizeof()" : "#"+_fname; } - @Override public String glabel() { return label(); } @Override public String uniqueName() { return "Off_" + _nid; } @Override diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java index 6e9b0a70..141fc357 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java @@ -25,7 +25,6 @@ public class FunPtrNode extends TypeNode { public FunNode fun() { return ret().fun(); } @Override public String label() { return "#"+_con; } - @Override public String glabel() { return "#"+_con.gprint(); } @Override public String uniqueName() { return "FunPtr_" + _nid; } @Override public Node copy() { FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java index fb93964a..d37655b6 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -31,11 +31,8 @@ public class LoadNode extends MemOpNode { } @Override public Tag serialTag() { return Tag.Load; } - // GraphVis DOT code (must be valid Java identifiers) and debugger labels + // Debugger label @Override public String label() { return "ld_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 2261adab..7c3d49f2 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -20,7 +20,6 @@ public class MinusNode extends Node implements ModeNode { } @Override public Tag serialTag() { return Tag.Minus; } - @Override public String glabel() { return "-"; } @Override public byte mode() { return _mode; } @Override public Node setMode(byte mode) { assert _mode==0 && (mode==1 || mode==2); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java index 26a9c0fd..3e1d78dc 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -32,10 +32,7 @@ public class NewNode extends Node implements MultiNode { return new NewNode(ts,null,null); } - @Override public String glabel() { - return _ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str(); - } - @Override public String label() { return "new_"+glabel(); } + @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java index d113a02a..e2148244 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -151,14 +151,12 @@ public abstract class Node implements Cloneable { // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public String label() { return serialTag().toString(); } - // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + // Unique label for debugging, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { - // Get rid of $ as graphviz doesn't like it - String label = label().replaceAll("\\$", ""); - return label + _nid; + return label() + _nid; } - // Graphical label, e.g. "+" or "Region" or "==" + // Operator symbol used by assembly/debug printing. public String glabel() { return label(); } // Extra fun stuff, for assembly printing. Jump labels, parser locations, diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java index 22b5bffe..ed6091fe 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -8,8 +8,6 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public Tag serialTag() { return Tag.Not; } - @Override public String glabel() { return "!"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java index 31d0565b..9d50d1a9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -10,9 +10,6 @@ public class OrNode extends ArithNode { @Override public String label() { return "Or"; } @Override public String op() { return "|"; } - @Override public String glabel() { return "|"; } - - @Override long doOp( long x, long y ) { return x | y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 04949a53..be36e8d9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -36,8 +36,6 @@ public class ParmNode extends PhiNode { @Override public String label() { return MemOpNode.mlabel(_label); } - @Override public String glabel() { return _label; } - @Override boolean _upgradeType( HashMap TYPES) { Type t = _declaredType.upgradeType(TYPES); if( t == _declaredType ) return false; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 54cfc3ad..09c55cde 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -47,8 +47,6 @@ public class PhiNode extends Node { @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - @Override public String glabel() { return "φ_"+_label; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( !(region() instanceof RegionNode r) || r.inProgress() ) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java index 02f77f36..f0e7ae95 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java @@ -19,7 +19,6 @@ public class PtrToIntNode extends Node { public PtrToIntNode(PtrToIntNode ptr) { super(ptr); } @Override public Tag serialTag() { return Tag.PtrToInt; } - @Override public String glabel() { return "(i64*)"; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(i64*)"),visited); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 322a6bc0..61809fe3 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -9,8 +9,6 @@ public class RoundF32Node extends Node { public RoundF32Node(Node lhs) { super(null, lhs); } @Override public Tag serialTag() { return Tag.RoundF32; } - @Override public String glabel() { return "(f32)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("((f32)"), visited).append(")"); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java index e3f99ed4..79b7d0e4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -9,8 +9,6 @@ public class SarNode extends ArithNode { @Override public Tag serialTag() { return Tag.Sar; } @Override public String op() { return ">>"; } - @Override public String glabel() { return ">>"; } - @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( y.isConstant() ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 24d9bdb4..1ca0ba3a 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -10,8 +10,6 @@ public class ShlNode extends ArithNode { @Override public Tag serialTag() { return Tag.Shl; } @Override public String op() { return "<<"; } - @Override public String glabel() { return "<<"; } - @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { if( x == TypeInteger.ZERO ) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 1bf7d118..7fad6275 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -9,8 +9,6 @@ public class ShrNode extends ArithNode { @Override public Tag serialTag() { return Tag.Shr; } @Override public String op() { return ">>>"; } - @Override public String glabel() { return ">>>"; } - @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return x == TypeInteger.ZERO ? x : TypeInteger.BOT; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java index faa3d17b..12a94906 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -49,10 +49,8 @@ public class StoreNode extends MemOpNode { baos.write(_size); } - // GraphVis DOT code and debugger labels + // Debugger label @Override public String label() { return "st_"+mlabel(); } - // GraphVis node-internal labels - @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } public Node nnptr() { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 2d8cc788..15350afb 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -10,8 +10,6 @@ public class ToFloatNode extends Node { public ToFloatNode(Node lhs) { super(null, lhs); } @Override public Tag serialTag() { return Tag.ToFloat; } - @Override public String glabel() { return "(flt)"; } - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return in(1)._print0(sb.append("(flt)"), visited); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java index 27ebc3dc..2ee816fa 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -9,8 +9,6 @@ public class XorNode extends ArithNode { @Override public Tag serialTag() { return Tag.Xor; } @Override public String op() { return "^"; } - @Override public String glabel() { return "^"; } - @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { return TypeInteger.BOT; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java deleted file mode 100644 index ad2df8b6..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CompUnit; -import com.seaofnodes.simple.node.*; -import java.util.*; -import static com.seaofnodes.simple.util.Utils.TODO; - -/** - * Simple visualizer that outputs GraphViz dot format. - * The dot output must be saved to a file and run manually via dot to generate the SVG output. - * Currently, this is done manually. - */ -public class GraphVisualizer { - - /** - * If set to true we put the control nodes in a separate cluster from - * data nodes. - */ - boolean _separateControlCluster = false; - - public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } - public GraphVisualizer() { this(false); } - - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._ref,parse._scope,parse._xScopes); } - public String generateDotOutput(CompUnit cu, Node scope, Stack xScopes) { - - // Since the graph has cycles, we need to create a flat list of all the - // nodes in the graph. - Collection all = findAll(xScopes, cu._stop, scope); - StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter18 {\n"); - sb.append("/*\n"); - sb.append(cu._src); - sb.append("\n*/\n"); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("\tordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.append("\tconcentrate=\"true\";\n"); - - // Force nested scopes to order - sb.append("\tcompound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, all); - - // Walk the active Scope edges - if( xScopes != null ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - sb.append("}\n"); - return sb.toString(); - } - - private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { - if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster - return; - // Just the Nodes first, in a cluster no edges - sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n instanceof XCtrlNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; - if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; - sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.glabel(); - if (n instanceof MultiNode) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb.append("shape=plaintext label=<\n"); - sb.append("\t\t\t
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - MemMergeNode n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart = scope._kinds.at(level)._lexSize; - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter24/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index f4f72a3b..00000000 --- a/chapter24/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -import java.util.Collection; -import java.util.Stack; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - static boolean SHOW; - - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static int N; // Dot frames - - JSViewer() throws Exception { - // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url="); - } - - void run( ) throws Exception { - SERVER.put("!"); - while( true ) { - String src = SERVER.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - N=0; - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - SHOW = false; - SERVER.put("#"); // Final frame - } - break; - } - } - } - - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - Stack xScopes = midParse ? CODE.P._xScopes : null; - - Collection all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null); - SB sb = new SB(); - sb.p("digraph view_").p(N++).p(" {\n").ii(); - if( midParse ) - sb.i().p("// POS: ").p(CODE.P.pos()).nl(); - - // To keep the Scopes below the graph and pointing up into the graph we - // need to group the Nodes in a subgraph cluster, and the scopes into a - // different subgraph cluster. THEN we can draw edges between the - // scopes and nodes. If we try to cross subgraph cluster borders while - // still making the subgraphs DOT gets confused. - sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes - - // CNC - turned off Apr/8/2024, gives more flex in the layout and - // removes some of the more ludicrous layout choices. - // Preserve node input order - //sb.append("ordering=\"in\";\n"); - - // Merge multiple edges hitting the same node. Makes common shared - // nodes much prettier to look at. - sb.i().p("concentrate=\"true\";\n"); - - // Force nested scopes to order - sb.i().p("compound=\"true\";\n"); - - // Just the Nodes first, in a cluster no edges - nodes(sb, all); - - // Now the scopes, in a cluster no edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scope( sb, xscope ); - - // Walk the Node edges - nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all); - - // Walk the active Scope edges - if( midParse ) - for( ScopeNode xscope : xScopes ) - if( !xscope.isDead() ) - scopeEdges( sb, xscope ); - - - sb.p("}\n").di(); - // Tell client another DOT frame - String dot = sb.toString(); - System.out.println(dot); - try { - SERVER.put(dot); - } catch( IOException ioe ) { - try { SERVER.close();} catch( IOException ignored ){} - } - } - - - private static void nodes(SB sb, Collection all) { - // Just the Nodes first, in a cluster no edges - sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name - for( Node n : all ) { - if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode ) - continue; // Do not emit, rolled into MultiNode or Scope cluster already - sb.i().p(n.uniqueName()).p(" [ "); - if( n instanceof MultiNode && !(n instanceof StartNode) ) { - // Make a box with the MultiNode on top, and all the projections on the bottom - sb. p("shape=plaintext label=<\n").ii(); - sb.i().p("\n"); - // Row over single cell for node label - cell(sb.i().p(""), n.glabel(), n, null).p("\n"); - // Row over single cell, for nested table - sb.i().p("\n"); - sb.i().p("
    \n").ii(); - sb.i().p("").p("\n"); - sb.i().p(""); - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid))); - boolean empty_row=true; - for( Node use : n._outputs ) { - int idx = idx(n); - if( idx != -1 ) { - cell(sb,use.glabel(),use,"p"+idx); - empty_row=false; - } - } - // At least one cell on row - if( empty_row ) sb.p(""); - sb. p("").p("\n"); - sb.i().p("
    ").p("\n").di(); - sb.i().p("
    >\n").di(); - sb.i().p("];\n"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow "); - node(sb,n.glabel(),n); - sb.p("];\n"); - } - } - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.i().p("{ rank=same; "); - sb.p(region.uniqueName()).p(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";"); - sb.p("}\n"); - } - } - sb.di().i().p("}").nl(); // End Node cluster - } - - // Build a nested scope display, walking the _prev edge - private static void scope( SB sb, ScopeNode scope ) { - sb.i().p("node [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth()+1; // One more than lexsize - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name - sb.i().p(scopeName).p(" [label=<\n").ii(); - sb.i().p("\n"); - int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize; - - // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { - sb.i().p(""); - for( int m=2; m\n"); // End scope level - } - - // Scope variables, empty if none - if( lexStart\n"); - for( int j=lexStart; j\n"); - } - sb.i().p("
    >];\n").di(); - // Scope label - sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - for( int i=0; i"),text,n,dark(color)).p("
    \n"); - sb.append("\t\t\t\n"); - sb.append("\t\t\t"); - boolean doProjTable = false; - n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); - for (Node use : n._outputs) { - if (use instanceof ProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append("
    ").append(lab).append("
    ").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - if (use instanceof CProjNode proj) { - if (!doProjTable) { - doProjTable = true; - sb.append(""); - } - sb.append("\n"); - sb.append("\t\t\t
    ").append(proj.glabel()).append("").append("\n"); - sb.append("\t\t\t\t").append("\n"); - sb.append("\t\t\t\t"); - } - sb.append(""); - } - } - if (doProjTable) { - sb.append("").append("\n"); - sb.append("\t\t\t\t
    ").append(proj.glabel()).append("
    ").append("\n"); - sb.append("\t\t\t
    >\n\t\t"); - - } else { - // control nodes have box shape - // other nodes are ellipses, i.e. default shape - if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); - else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); - sb.append("label=\"").append(lab).append("\" "); - } - sb.append("];\n"); - } - if (!_separateControlCluster) { - // Force Region & Phis to line up - for (Node n : all) { - if (n instanceof RegionNode region) { - sb.append("\t\t{ rank=same; "); - sb.append(region.uniqueName()).append(";"); - for (Node phi : region._outputs) - if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); - sb.append("}\n"); - } - } - } - sb.append("\t}\n"); // End Node cluster - } - - private void nodes(StringBuilder sb, Collection all) { - nodesByCluster(sb, true, all); - nodesByCluster(sb, false, all); - } - - // Build a nested scope display, walking the _prev edge - private void scope( StringBuilder sb, ScopeNode scope ) { - sb.append("\tnode [shape=plaintext];\n"); - int last = scope.nIns(); - int max = scope.depth(); - for( int i = 0; i < max; i++ ) { - int level = max-i-1; - String scopeName = makeScopeName(scope, level); - sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name - // Special for memory ScopeMinNode - if( level==0 ) { - Node n = scope.mem(); - sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); - } - sb.append("\t\t").append(scopeName).append(" [label=<\n"); - sb.append("\t\t\t\n"); - // Add the scope level - sb.append("\t\t\t"); - int lexStart = scope._kinds.at(level)._lexSize; - for( int j=lexStart; j").append(v._name).append(""); - } - last = lexStart; - sb.append("\n"); - sb.append("\t\t\t
    ").append(level).append("
    >];\n"); - } - // Scope clusters nest, so the graphics shows the nested scopes, so - // they are not closed as they are printed; so they just keep nesting. - // We close them all at once here. - sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters - } - - private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - - // Walk the node edges - private void nodeEdges(StringBuilder sb, Collection all) { - // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); - for( Node n : all ) { - // Do not display the Constant->Start edge; - // ProjNodes handled by Multi; - // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n instanceof XCtrlNode ) - continue; - for( int i=0; idef edge from Phi to Region. - sb.append('\t').append(n.uniqueName()); - sb.append(" -> "); - sb.append(def.uniqueName()); - sb.append(" [style=dotted taillabel=").append(i).append("];\n"); - } else if( def != null ) { - // Most edges land here use->def - sb.append('\t').append(n.uniqueName()).append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - String mname = proj.in(0).uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - // Number edges, so we can see how they track - sb.append("[taillabel=").append(i); - // The edge from New to ctrl is just for anchoring the New - if ( n instanceof NewNode ) - sb.append(" color=green"); - // control edges are colored red - else if( def instanceof CFGNode ) - sb.append(" color=red"); - else if( def.isMem() ) - sb.append(" color=blue"); - // Backedges do not add a ranking constraint - if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); - sb.append("];\n"); - } - } - } - } - - // Walk the scope edges - private void scopeEdges( StringBuilder sb, ScopeNode scope ) { - sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=0; - for( var v : scope._vars ) { - Node def = scope.in(v._idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(v._idx); - if( def==null ) continue; - while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize ) - level++; - String scopeName = makeScopeName(scope, level-1); - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else if( def instanceof ProjNode proj ) { - sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - } - - /** - * Finds all nodes in the graph. - */ - public static Collection findAll(Stack ss, Node... ns) { - final HashMap all = new HashMap<>(); - for( Node n : ns ) - walk(all, n); - if( ss != null ) - for( Node n : ss ) - walk(all, n); - return all.values(); - } - - /** - * Walk a subgraph and populate distinct nodes in the all list. - */ - private static void walk(HashMap all, Node n) { - if(n == null ) return; - if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - walk(all, c); - for( Node c : n._outputs ) - walk(all, c); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java deleted file mode 100644 index 03842119..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.*; -import com.seaofnodes.simple.codegen.CodeGen; -import java.io.IOException; - -public class JSViewer extends GraphViewer { - public static void main(String[] args) throws IOException { - try( var view = new JSViewer() ) { view.run(); } - } - JSViewer() throws IOException {} - - @Override protected void compile(String src) { - CodeGen code = new CodeGen(src); - code._obs = new SimpleGraphObserver(code) { - @Override protected void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException { - JSViewer.this.frame(snap, pos, evt); - } - }; - try { - code.parse().opto(); - } finally { - code._obs = null; - } - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 0318b793..7af718ac 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -1,17 +1,30 @@ 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 abstract class SimpleGraphObserver extends GraphCapture { - private final CodeGen _code; - protected SimpleGraphObserver(CodeGen code) { super(new SimpleGraphAdapter()); _code = code; } +public class SimpleGraphObserver extends GraphCapture { + 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.parse().opto(); + } @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() { _code._obs = null; } + @Override protected void detach() { + if( _code != null ) _code._obs = null; + _code = null; + } @Override protected void roots(ArrayList roots) { roots.add(_code._stop); if( _code._phase == CodeGen.Phase.Parse ) { diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java index 91f690eb..b7e10122 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java @@ -54,7 +54,6 @@ Options: --dump-after-encode - dump intermediate representation after encoding pass --dump-after-all - dump intermediate representation after all passes --dump - dump final intermediate representation only - --dot - dump grapical representation of intermediate code into *.dot file(s) """ ); System.exit(0); @@ -91,7 +90,6 @@ Options: boolean do_run = true; boolean do_codegen = false; boolean emit_main = false; - boolean dump_dot = false; boolean print_time = false; boolean print_size = false; boolean print_asm = false; @@ -126,7 +124,6 @@ Options: case "--dump-after-export": dump |= 1<` before the main class. -The viewer uses a WebSocket connection. Chapters 4 and 25 send structured -JSON snapshots in each frame; chapters 18–24 still send bare DOT. +The viewer uses a WebSocket connection. Every chapter sends structured +JSON snapshots in each frame. No JavaScript package installation, frontend build, or separate server process is required. For development over HTTP, run this from the repository root: @@ -32,7 +30,7 @@ The initial program compiles on connection. For another program, click **Compile or press **Ctrl+Enter** (**Cmd+Enter** on macOS), then use the arrows to step through the captured frames. Keep `make view` running while using the tab. -In chapters 4 and 25, scroll to zoom, drag to pan, and use **Fit** to show the whole +Scroll to zoom, drag to pan, and use **Fit** to show the whole graph again. Clicking a node shows its full label, type and input slots, and highlights its edges. Selection follows that node's ID across frames; Escape clears it. **Associations** shows scope bindings and lifetime edges, which are @@ -70,23 +68,24 @@ for Firefox testing. Test logs/screenshots are written under `build/graph-browse ## Boundary between chapters and the viewer -Chapters 4 and 25 share the Java viewer plumbing as well as the browser: +All chapters share the Java viewer plumbing as well as the browser: -- `GraphViewer` owns asset discovery, the source/compile loop, frame serialization, - and session shutdown. A chapter's `JSViewer` subclass only invokes its compiler - and attaches/detaches its observer. +- `GraphViewer` owns asset discovery, the source/compile loop and session shutdown. + Each chapter launches it from `SimpleGraphObserver.main`; no separate launcher + class is needed. - `GraphSocket` serves local assets, opens the browser, and handles WebSocket messages. One buffered stream handles both the upgrade and frames. Payloads use exact reads and their advertised length, including 64-bit lengths; continuation frames assemble UTF-8 before decoding, and PING data is echoed. - `GraphCapture` owns nested attempts, buffered snapshots, event numbering, - neighborhood accumulation and disconnect handling. It extends `GraphObserver`. - Chapter subclasses supply roots, phase, parser position and observer detachment. + neighborhood accumulation, serialization and disconnect handling. It extends + `GraphObserver`. Chapter subclasses compile source, attach/detach the observer, + and supply roots, phase and parser position. Each compilation gets fresh capture + state and a new compilation key, even when the previous compilation failed. - `GraphAdapter` supplies node facts and indexed graph/dependency access. Dependency access defaults to an empty set in chapters without those lists. -Chapter 25's private WebSocket implementation is removed. Chapters 18–24 retain -their old transport until migrated. Shared Java has no dependency on chapter classes. +Shared Java has no dependency on chapter classes. The WebSocket listener and built-in asset server bind to 127.0.0.1. The connection messages are: @@ -95,8 +94,7 @@ The connection messages are: | --- | --- | --- | | Compiler to browser | `!` | Request the source program | | Browser to compiler | Source text | Compile and capture a new sequence | -| Compiler to browser | `digraph ...` | One complete DOT snapshot | -| Compiler to browser | JSON `{snap, pos, evt}` | Chapters 4/25's complete graph, parser position and event | +| Compiler to browser | JSON `{snap, pos, evt}` | Complete graph, parser position and event | | Compiler to browser | JSON `{error}` | Compilation failed; the session accepts another program | | Browser to compiler | `+` | Acknowledge/request another frame | | Compiler to browser | `#` | End of sequence | @@ -134,10 +132,7 @@ edges have stable SVG IDs within a compilation. There is no animation yet. One `+` acknowledges the whole frame. The shared writer sends UTF-8 bytes and supports WebSocket's 64-bit length header for frames larger than 65535 bytes. -Legacy chapters' DOT frames may contain a `// POS:` comment identifying the -parser position. The browser owns frame history and backward/forward playback. - -Chapter 25 no longer generates DOT for the interactive viewer. The browser +The browser owns frame history and backward/forward playback. It uses ELK's layered layout with fixed input ports and orthogonal routing. Layout follows def-to-use flow downward, while the displayed arrows point from use to def, matching Simple's edges. Control edges get a higher layout @@ -157,7 +152,7 @@ ELK runs locally from the pinned elkjs 0.12.0 worker; see No npm or Maven step is needed to use it. The linearization workflow includes this directory as a shared root resource. -Chapters 4 and 25 compile the shared Java sources from this directory; the +All chapters compile the shared Java sources from this directory; the interactive viewer also loads the web assets from this checkout. ## Chapter adapters @@ -177,9 +172,8 @@ root deduplication; no boxed integer lists, sets or map keys are needed. Indexed edge access reads each chapter's native storage without copying it into a different collection type. -Both chapters implement `com.seaofnodes.simple.print.SimpleGraphAdapter`, -extending `GraphAdapter`. No changes to `Node` or its subclasses are -needed. The chapter owns classification and extraction; the base owns traversal +Every chapter implements `com.seaofnodes.simple.print.SimpleGraphAdapter`, +extending `GraphAdapter`. The chapter owns classification and extraction; the base owns traversal and snapshot assembly. No new Java interface is involved. For chapter 4: @@ -223,7 +217,7 @@ Functions and compilation-unit boundaries. These are existing IR facts, not computed region membership or a SESE hierarchy. Scope bindings and constant lifetime edges are associations, so they need not constrain CFG layout. Types may be absent on newly constructed nodes. Source locations, grouping, -and delta encoding remain follow-up work. Both chapters use the observer events +and delta encoding remain follow-up work. All chapters use the observer events described below. Make compiles these shared sources directly into each participating chapter's @@ -237,28 +231,28 @@ make -C chapter25 build `build` compiles the compiler and adapter without running tests or opening a browser. Shared source changes trigger recompilation. No Maven executable, Maven-built artifacts, generated source copies or separately installed graph -JARs are needed. Existing `make tests`, `make release` and chapter 25's -`make view` also include the shared sources through their normal prerequisites. -Chapter 4's `make view` depends on `build` and runs the same shared viewer. +JARs are needed. Every `make view` depends on `build` through `graph/graph.mk`. Existing +`make tests` and `make release` also include the shared sources through their +normal prerequisites. For the existing Maven/CI build path, `build-helper:add-source` describes the -same source directory. The two chapter POMs select `../graph`, while the root +same source directory. Chapter POMs select `../graph`, while the root POM defaults to `graph` for linearized checkouts. These entries are independent of the Make build. ## Peephole observers `GraphObserver` is a shared abstract base for `before`, `after`, `phase`, and -`dep` callbacks. Chapter 25's `CodeGen._obs` and chapter 4's `Parser._obs` own the -optional observer. Chapter 4 uses `Parser.PARSER` as its current compilation -context, alongside its existing global `START`. Normal compilation +`dep` callbacks. `Parser._obs` (chapters 2–17) or `CodeGen._obs` (chapters 18–25) owns the +optional observer. Parser-only chapters use `Parser.PARSER` as their current +compilation context, alongside the existing global `START`. Normal compilation leaves it null, so it does no snapshot capture or serialization. Assertions that probe peepholes with `_midAssert` set do not generate observer calls. -Neither `Node` nor `IterPeeps` refers to `JSViewer`. +Compiler hooks refer only to the shared observer base. `GraphCapture` supplies capture and neighborhood logic. Each chapter's small -`SimpleGraphObserver` supplies its compilation context. `JSViewer` installs one -for each compilation and forwards frames to `GraphViewer`. +`SimpleGraphObserver` supplies its compilation context. The shared capture base +resets its state before each compilation and detaches the observer afterward. An I/O failure disables the observer and allows optimization to continue. The observer is detached in a `finally` block when compilation finishes or fails. @@ -269,7 +263,7 @@ The event kinds distinguish the actual capture boundaries: | `BEFORE` | Graph before an attempt that made progress, or contained a nested rewrite | | `RETURN` | Recursive `Node.peephole()` finished, including its local DCE; the caller has not yet attached the returned node | | `APPLY` | A worklist attempt finished after substitution, dependency draining, and unused-node cleanup | -| `PHASE` | Parse completed in chapter 4; Parse, Iter, or Opto completed in chapter 25, including work outside individual peepholes | +| `PHASE` | Parse, Iter, or Opto completed, including work outside individual peepholes | `peep` identifies an attempt; `up` identifies its enclosing attempt, or zero. `node` and `repl` identify the original and result nodes. A zero `repl` on @@ -282,7 +276,7 @@ progress are discarded unless they contain a visible child rewrite. Ancestor before frames are emitted before their children; after frames close the nested attempts. Frame steps are assigned consecutively on emission, while attempt IDs may have gaps. A failed compilation can leave an unfinished attempt; the next -compilation gets a fresh observer and compilation key. +compilation starts with empty capture state and a new compilation key. Capture includes the active attempts and returned node as extra roots, so nodes not yet attached by the parser remain visible. Dead originals are not added as @@ -309,15 +303,16 @@ Actual graph diffs and explicit rule annotations can refine this later. The browser keeps the event alongside its snapshot and layout. It labels and highlights the current step, but does not yet collapse nested rewrites or move the camera to their neighborhoods. Chapter 4 uses the same event model, with -defs/uses for its neighborhoods and no worklist or dependency lists. Its parser -retains the final Return as a root after scope cleanup. - -Chapter 25's parser no longer accepts `#showGraph;`; use the browser to inspect -the graph and **Save SVG** for a static figure. Earlier chapters retain their -directive until migrated. Chapter 25 still supports the independent `--dot` -dump option; it and `GraphVisualizer` can be retired separately. - -Constant-folding peepholes already exist in chapter02. Chapters 03 and 04 add -variables and algebraic rewrites, respectively, and are useful next adapters. -They should not need the later chapters' `CodeGen`, worklist, or type hierarchy -to use the shared display. +defs/uses for its neighborhoods and no worklist or dependency lists. It retains the final Return as a root after scope cleanup. + +The compiler DOT dump flag, parser graph directive, Java DOT generators and old +viewer/transport classes have been removed from all chapters. Use **Save SVG** +for new documentation figures. Existing `.gv` sources, their checked-in SVGs +and the documentation Make rules for regenerating those SVGs remain. + +Chapter 1 has no peepholes and supplies one completed parse snapshot. Chapter 2 +shows constant folding; chapters 3 and 4 add variables and algebraic rewrites. +Chapters 9–17 also capture worklist application and dependency neighborhoods; +chapters 18–25 use their CodeGen phase/context. Each chapter retains only its +`SimpleGraphAdapter`, `SimpleGraphObserver`, an optional observer field and the +compiler callbacks/accessors needed by those two classes. diff --git a/graph/graph.mk b/graph/graph.mk new file mode 100644 index 00000000..1d5eb95a --- /dev/null +++ b/graph/graph.mk @@ -0,0 +1,11 @@ +# Include after defining chapter source variables. Works in linearized checkouts too. +GRAPH_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) +graph_javas := $(wildcard $(GRAPH_DIR)/src/main/java/com/seaofnodes/graph/*.java) +main_javas += $(graph_javas) + +# Defining view here must not change the chapter's default target. +graph_goal := $(.DEFAULT_GOAL) +.PHONY: view +view: build + java -ea -cp "$(CLZDIR)/main" com.seaofnodes.simple.print.SimpleGraphObserver +.DEFAULT_GOAL := $(graph_goal) diff --git a/graph/src/main/java/com/seaofnodes/graph/GraphCapture.java b/graph/src/main/java/com/seaofnodes/graph/GraphCapture.java index 0e2f538d..6fc40b5f 100644 --- a/graph/src/main/java/com/seaofnodes/graph/GraphCapture.java +++ b/graph/src/main/java/com/seaofnodes/graph/GraphCapture.java @@ -5,23 +5,43 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.UUID; -/** Shared capture, event nesting and neighborhoods. Chapter hooks only read the IR. */ +/** Shared compilation lifecycle, capture, event nesting and neighborhoods. */ public abstract class GraphCapture extends GraphObserver { private final GraphAdapter _graph; - private final String _comp = UUID.randomUUID().toString(); + private String _comp; + private GraphSocket _server; private final ArrayList _peeps = new ArrayList<>(); private long _next, _step; - private boolean _off; + private boolean _off = true; protected GraphCapture(GraphAdapter graph) { _graph = graph; } + // Create the chapter's compiler, attach this observer, and run its phases. + protected abstract void compile(String src); + // The remaining hooks only inspect context, except detach which clears it. protected abstract String phase(); protected abstract int pos(); protected abstract void roots(ArrayList roots); protected abstract void detach(); - protected abstract void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException; + final void run(String src, GraphSocket server) { + _comp = UUID.randomUUID().toString(); + _next = _step = 0; + _server = server; + _off = false; + try { + compile(src); + } finally { + _off = true; + try { + detach(); + } finally { + _peeps.clear(); + _server = null; + } + } + } private class Peep { final N node; @@ -114,11 +134,10 @@ public abstract class GraphCapture extends GraphObserver { // Assign steps on emission; attempts without progress do not consume frames. snap = new GraphSnapshot(snap.ver(), _comp, _step++, snap.roots(), snap.nodes()); try { - frame(snap, pos, evt); + _server.put(GraphJson.frame(snap, pos, evt)); } catch( IOException e ) { // A disconnected display must not interrupt optimization. _off = true; - detach(); System.err.println("Graph viewer disconnected: " + e.getMessage()); } } diff --git a/graph/src/main/java/com/seaofnodes/graph/GraphViewer.java b/graph/src/main/java/com/seaofnodes/graph/GraphViewer.java index dcb2e337..e70a12cf 100644 --- a/graph/src/main/java/com/seaofnodes/graph/GraphViewer.java +++ b/graph/src/main/java/com/seaofnodes/graph/GraphViewer.java @@ -5,16 +5,9 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -/** Shared launcher and source/compile loop. Subclasses supply their compiler. */ -public abstract class GraphViewer implements AutoCloseable { - private final GraphSocket _server; - - protected GraphViewer() throws IOException { _server = new GraphSocket(viewerURI(), 12345); } - protected abstract void compile(String src); - - protected final void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException { - _server.put(GraphJson.frame(snap, pos, evt)); - } +/** Shared launcher and source/compile loop. Chapter observers supply their compiler. */ +public final class GraphViewer { + private GraphViewer() {} private static URI viewerURI() throws IOException { String url = System.getProperty("simple.graph.url"); @@ -27,23 +20,23 @@ public abstract class GraphViewer implements AutoCloseable { + "or set -Dsimple.graph.url="); } - public final void run() throws IOException { - _server.put("!"); - while( true ) { - String src = _server.get(); - if( src == null || src.equals("null") ) return; - if( src.equals("+") ) continue; // Frames are currently sent eagerly. - System.out.println(src); - try { - compile(src); - } catch( RuntimeException e ) { - System.err.println(e); - _server.put(GraphJson.error(e.toString())); - } finally { - _server.put("#"); + public static void run(GraphCapture obs) throws IOException { + try( var server = new GraphSocket(viewerURI(), 12345) ) { + server.put("!"); + while( true ) { + String src = server.get(); + if( src == null || src.equals("null") ) return; + if( src.equals("+") ) continue; // Frames are currently sent eagerly. + System.out.println(src); + try { + obs.run(src, server); + } catch( RuntimeException e ) { + System.err.println(e); + server.put(GraphJson.error(e.toString())); + } finally { + server.put("#"); + } } } } - - @Override public final void close() throws IOException { _server.close(); } } diff --git a/graph/test_browser.py b/graph/test_browser.py index 90223119..52cb5fa6 100644 --- a/graph/test_browser.py +++ b/graph/test_browser.py @@ -23,14 +23,14 @@ from playwright.sync_api import sync_playwright def check(chapter, browser_name): number = int(chapter.removeprefix("chapter")) - package = "com.seaofnodes.simple." + ("print." if number == 4 or number >= 20 else "") + package = "com.seaofnodes.simple.print." artifacts = ROOT / "build/graph-browser" / (chapter + "-" + browser_name) artifacts.mkdir(parents=True, exist_ok=True) log = artifacts / "compiler.log" with log.open("w") as output: process = subprocess.Popen([ "java", "-ea", "-Dsimple.graph.open=false", "-cp", "build/classes/main", - package + "JSViewer"], cwd=ROOT/chapter, stdout=output, stderr=subprocess.STDOUT) + package + "SimpleGraphObserver"], cwd=ROOT/chapter, stdout=output, stderr=subprocess.STDOUT) try: for _ in range(200): text = log.read_text() @@ -49,12 +49,15 @@ def check(chapter, browser_name): page.wait_for_function("done && current === 0 && !rendering", timeout=15000) assert page.locator("#graph svg g.node").count() > 0 assert page.locator("#doPrev").is_disabled() - page.locator("#doNext").click() - page.wait_for_function("current === 1 && !rendering") - page.locator("#doPrev").click() - page.wait_for_function("current === 0 && !rendering") - sources = ("return 1 + 2;", "int x=arg+1; return x+x+2;" if number == 4 else - "int x=0; while(x 1: + page.locator("#doNext").click() + page.wait_for_function("current === 1 && !rendering") + page.locator("#doPrev").click() + page.wait_for_function("current === 0 && !rendering") + sources = (("return 1;", "return 2;") if number == 1 else + ("return 1 + 2;", "return 3 * 4;") if number == 2 else + ("return 1 + 2;", "int x=arg+1; return x+x+2;") if number <= 5 else + ("return 1 + 2;", "int x=0; while(x generation > previous && done && current === 0 && !rendering", arg=previous, timeout=15000) total = int(page.locator("#len").inner_text()) - assert total > 1 - for index in range(1, total): - page.locator("#doNext").click() + assert total >= 1 + # Later chapters also parse library code. Sample long histories. + steps = (range(1, total) if total <= 100 else + sorted({1, total // 4, total // 2, 3 * total // 4, total - 2, total - 1})) + print(chapter, total, "frames captured; rendering", len(steps) + 1, flush=True) + prev = 0 + for index in steps: + if index == prev + 1: + page.locator("#doNext").click() + else: + page.evaluate("index => render(index)", index) page.wait_for_function("index => current === index && !rendering", arg=index) + prev = index assert page.locator("#doNext").is_disabled() assert "error" not in page.locator("#status").inner_text().lower() - if number in (4, 25): + if total > 1: assert page.locator("#elk g.node").count() == page.evaluate("frames[current].snap.nodes.length") # Revisit cached geometry and keep the selected node across steps. page.evaluate("""() => { @@ -86,7 +98,7 @@ def check(chapter, browser_name): page.evaluate("delete layout.run") assert page.locator("#detail").is_visible() page.keyboard.press("Escape") - print(chapter, browser_name, total, "frames rendered:", source, flush=True) + print(chapter, browser_name, "playback passed:", source, flush=True) assert not errors, errors page.screenshot(path=str(artifacts / "viewer.png")) page.locator("#doExit").click() @@ -117,7 +129,7 @@ def check(chapter, browser_name): if process.poll() is None: process.terminate() process.wait(timeout=5) - print(chapter, browser_name, "startup, forward/back, recompile, all frames, disconnect, and offline startup passed") + print(chapter, browser_name, "startup, forward/back, recompile, playback, disconnect, and offline startup passed") if __name__ == "__main__": diff --git a/graph/test_transport.py b/graph/test_transport.py index d0026b97..f1834403 100644 --- a/graph/test_transport.py +++ b/graph/test_transport.py @@ -46,15 +46,15 @@ def compile_frames(stream): def check(chapter): root = Path(__file__).resolve().parent.parent number = int(chapter.removeprefix("chapter")) - assert number == 4 or 18 <= number <= 25 + assert 1 <= number <= 25 chapter_dir = root / chapter - package = "com.seaofnodes.simple." + ("print." if number == 4 or number >= 20 else "") + package = "com.seaofnodes.simple.print." log = root / "build" / "graph-transport" / (chapter + ".log") log.parent.mkdir(parents=True, exist_ok=True) with log.open("w") as output: process = subprocess.Popen( ["java", "-ea", "-Dsimple.graph.open=false", "-cp", - "build/classes/main", package + "JSViewer"], + "build/classes/main", package + "SimpleGraphObserver"], cwd=chapter_dir, stdout=output, stderr=subprocess.STDOUT) try: deadline = time.monotonic() + 10 @@ -69,9 +69,8 @@ def check(chapter): time.sleep(.05) assert url and url.startswith("http://127.0.0.1:"), log.read_text() # The browser must be able to load all assets before connecting. - for asset in ("index.html", "viewer.js", "layout.js", "render.js", "legacy.js", - "vendor/elk-api.js", "vendor/elk-worker.min.js", "vendor/d3.v7.min.js", - "vendor/d3-graphviz.js", "vendor/graphviz.umd.js"): + for asset in ("index.html", "viewer.js", "layout.js", "render.js", + "vendor/elk-api.js", "vendor/elk-worker.min.js", "vendor/d3.v7.min.js"): with urllib.request.urlopen(url.rsplit("/", 1)[0] + "/" + asset, timeout=3) as response: assert response.status == 200 @@ -93,9 +92,8 @@ def check(chapter): else: raise AssertionError("Responded before headers ended: " + repr(early)) client.settimeout(3) - shared = number in (4, 25) # A frame in the same write as the upgrade must not get lost in a reader buffer. - client.sendall(b"\r\n" + (packet(b'hello', op=9) if shared else b'')) + client.sendall(b"\r\n" + packet(b'hello', op=9)) with client.makefile("rb") as stream: assert stream.readline() == b"HTTP/1.1 101 Switching Protocols\r\n" headers = [] @@ -107,25 +105,24 @@ def check(chapter): headers.append(line) assert b"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" in headers assert stream.read(3) == b"\x81\x01!", "Missing source request" - if shared: - assert frame(stream) == (10, b'hello') - # Larger than both the old 1 KB buffer and the 16-bit length field. - data = packet(b' ' * 70000 + b'return 3;') - client.sendall(data[:3]) - time.sleep(.05) - for start in range(3, len(data), 509): - client.sendall(data[start:start + 509]) - frames = compile_frames(stream) - assert frames and all('snap' in f for f in frames) - # Split a UTF-8 code point across fragments, with an interleaved PING. - client.sendall(packet(b'\xc3', fin=False) + packet(b'ping', op=9) + packet(b'\xa9', op=0)) - assert frame(stream) == (10, b'ping') - errors = compile_frames(stream) - assert any('error' in f for f in errors) - assert '\u00e9' in log.read_text() - client.sendall(packet(b'return 4;')) - frames = compile_frames(stream) - assert frames and all('snap' in f for f in frames) + assert frame(stream) == (10, b'hello') + # Larger than both the old 1 KB buffer and the 16-bit length field. + data = packet(b' ' * 70000 + b'return 3;') + client.sendall(data[:3]) + time.sleep(.05) + for start in range(3, len(data), 509): + client.sendall(data[start:start + 509]) + frames = compile_frames(stream) + assert frames and all('snap' in f for f in frames) + # Split a UTF-8 code point across fragments, with an interleaved PING. + client.sendall(packet(b'return 3; \xc3', fin=False) + packet(b'ping', op=9) + packet(b'\xa9', op=0)) + assert frame(stream) == (10, b'ping') + errors = compile_frames(stream) + assert any('error' in f for f in errors) + assert '\u00e9' in log.read_text() + client.sendall(packet(b'return 4;')) + frames = compile_frames(stream) + assert frames and all('snap' in f for f in frames) # Masked 'null' asks the compiler to exit normally. mask = b"abcd" payload = bytes(byte ^ mask[i % 4] for i, byte in enumerate(b"null")) @@ -144,7 +141,7 @@ def check(chapter): process.terminate() process.wait(timeout=5) print(chapter + ": HTTP assets, fragmented upgrade, greeting, and shutdown passed" + - ("; large frames, UTF-8 fragments, ping, and compile-error recovery passed" if number in (4, 25) else "")) + "; large frames, UTF-8 fragments, ping, and compile-error recovery passed") if __name__ == "__main__": diff --git a/graph/web/index.html b/graph/web/index.html index f91f1710..771d3d4a 100644 --- a/graph/web/index.html +++ b/graph/web/index.html @@ -11,7 +11,7 @@ #program { width:min(600px,80vw); vertical-align:middle; } #status { margin-top:4px; } #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; } - #elk, #dot { width:100%; height:100%; } + #elk { width:100%; height:100%; } #graph svg { display:block; } .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; } .hint { color:#616c78; } @@ -42,7 +42,6 @@ -
    @@ -61,7 +60,7 @@
    -
    +
    0 diff --git a/graph/web/legacy.js b/graph/web/legacy.js deleted file mode 100644 index 4c1b7ef8..00000000 --- a/graph/web/legacy.js +++ /dev/null @@ -1,25 +0,0 @@ -// Only earlier chapters need Graphviz. Load it on the first bare-DOT frame. -let dotView; -async function drawDot(dot) { - if (!dotView) { - dotView = (async () => { - for (const src of ["vendor/graphviz.umd.js", "vendor/d3-graphviz.js"]) { - await new Promise((resolve, reject) => { - const script = document.createElement("script"); - script.src = src; script.onload = resolve; - script.onerror = () => reject(new Error("Cannot load " + src)); - document.head.append(script); - }); - } - return new Promise((resolve, reject) => { - const view = d3.select("#dot").graphviz({useWorker: false, fit: true}) - .onerror(reject).on("initEnd", () => resolve(view)) - .transition(() => d3.transition("main").ease(d3.easeLinear).duration(500)); - }); - })(); - } - const view = await dotView; - const host = document.getElementById("dot"); - view.width(host.clientWidth).height(host.clientHeight); - await new Promise((resolve, reject) => view.onerror(reject).renderDot(dot, resolve)); -} diff --git a/graph/web/vendor/d3-graphviz.js b/graph/web/vendor/d3-graphviz.js deleted file mode 100644 index 7439ba65..00000000 --- a/graph/web/vendor/d3-graphviz.js +++ /dev/null @@ -1,2014 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-dispatch'), require('d3-transition'), require('d3-timer'), require('d3-interpolate'), require('d3-zoom'), require('d3-format'), require('d3-path')) : - typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-dispatch', 'd3-transition', 'd3-timer', 'd3-interpolate', 'd3-zoom', 'd3-format', 'd3-path'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["d3-graphviz"] = {}, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3)); -})(this, (function (exports, d3, d3Dispatch, d3Transition, d3Timer, d3Interpolate, d3Zoom, d3Format, d3Path) { 'use strict'; - - function _interopNamespaceDefault(e) { - var n = Object.create(null); - if (e) { - Object.keys(e).forEach(function (k) { - if (k !== 'default') { - var d = Object.getOwnPropertyDescriptor(e, k); - Object.defineProperty(n, k, d.get ? d : { - enumerable: true, - get: function () { return e[k]; } - }); - } - }); - } - n.default = e; - return Object.freeze(n); - } - - var d3__namespace = /*#__PURE__*/_interopNamespaceDefault(d3); - - function _arrayLikeToArray(r, a) { - (null == a || a > r.length) && (a = r.length); - for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; - return n; - } - function _arrayWithoutHoles(r) { - if (Array.isArray(r)) return _arrayLikeToArray(r); - } - function _defineProperty(e, r, t) { - return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { - value: t, - enumerable: !0, - configurable: !0, - writable: !0 - }) : e[r] = t, e; - } - function _iterableToArray(r) { - if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); - } - function _nonIterableSpread() { - throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - function _toConsumableArray(r) { - return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); - } - function _toPrimitive(t, r) { - if ("object" != typeof t || !t) return t; - var e = t[Symbol.toPrimitive]; - if (void 0 !== e) { - var i = e.call(t, r || "default"); - if ("object" != typeof i) return i; - throw new TypeError("@@toPrimitive must return a primitive value."); - } - return ("string" === r ? String : Number)(t); - } - function _toPropertyKey(t) { - var i = _toPrimitive(t, "string"); - return "symbol" == typeof i ? i : i + ""; - } - function _unsupportedIterableToArray(r, a) { - if (r) { - if ("string" == typeof r) return _arrayLikeToArray(r, a); - var t = {}.toString.call(r).slice(8, -1); - return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; - } - } - - function extractElementData(element) { - var datum = {}; - var tag = element.node().nodeName; - datum.tag = tag; - if (tag == '#text') { - datum.text = element.text(); - } else if (tag == '#comment') { - datum.comment = element.text(); - } - datum.attributes = {}; - var attributes = element.node().attributes; - if (attributes) { - for (var i = 0; i < attributes.length; i++) { - var attribute = attributes[i]; - var name = attribute.name; - var value = attribute.value; - datum.attributes[name] = value; - } - } - var transform = element.node().transform; - if (transform && transform.baseVal.numberOfItems != 0) { - var matrix = transform.baseVal.consolidate().matrix; - datum.translation = { - x: matrix.e, - y: matrix.f - }; - datum.scale = matrix.a; - } - if (tag == 'ellipse') { - datum.center = { - x: datum.attributes.cx, - y: datum.attributes.cy - }; - } - if (tag == 'polygon') { - var points = element.attr('points').split(' '); - var x = points.map(function (p) { - return p.split(',')[0]; - }); - var y = points.map(function (p) { - return p.split(',')[1]; - }); - var xmin = Math.min.apply(null, x); - var xmax = Math.max.apply(null, x); - var ymin = Math.min.apply(null, y); - var ymax = Math.max.apply(null, y); - var bbox = { - x: xmin, - y: ymin, - width: xmax - xmin, - height: ymax - ymin - }; - datum.bbox = bbox; - datum.center = { - x: (xmin + xmax) / 2, - y: (ymin + ymax) / 2 - }; - } - if (tag == 'path') { - var d = element.attr('d'); - var points = d.split(/[A-Z ]/); - points.shift(); - var x = points.map(function (p) { - return +p.split(',')[0]; - }); - var y = points.map(function (p) { - return +p.split(',')[1]; - }); - var xmin = Math.min.apply(null, x); - var xmax = Math.max.apply(null, x); - var ymin = Math.min.apply(null, y); - var ymax = Math.max.apply(null, y); - var bbox = { - x: xmin, - y: ymin, - width: xmax - xmin, - height: ymax - ymin - }; - datum.bbox = bbox; - datum.center = { - x: (xmin + xmax) / 2, - y: (ymin + ymax) / 2 - }; - datum.totalLength = element.node().getTotalLength(); - } - if (tag == 'text') { - datum.center = { - x: element.attr('x'), - y: element.attr('y') - }; - } - if (tag == '#text') { - datum.text = element.text(); - } else if (tag == '#comment') { - datum.comment = element.text(); - } - return datum; - } - function extractAllElementsData(element) { - var datum = extractElementData(element); - datum.children = []; - var children = d3__namespace.selectAll(element.node().childNodes); - children.each(function () { - var childData = extractAllElementsData(d3__namespace.select(this)); - childData.parent = datum; - datum.children.push(childData); - }); - return datum; - } - function createElement(data) { - if (data.tag == '#text') { - return document.createTextNode(""); - } else if (data.tag == '#comment') { - return document.createComment(data.comment); - } else { - return document.createElementNS('http://www.w3.org/2000/svg', data.tag); - } - } - function createElementWithAttributes(data) { - var elementNode = createElement(data); - var element = d3__namespace.select(elementNode); - var attributes = data.attributes; - for (var _i = 0, _Object$keys = Object.keys(attributes); _i < _Object$keys.length; _i++) { - var attributeName = _Object$keys[_i]; - var attributeValue = attributes[attributeName]; - element.attr(attributeName, attributeValue); - } - return elementNode; - } - function replaceElement(element, data) { - var parent = d3__namespace.select(element.node().parentNode); - var newElementNode = createElementWithAttributes(data); - var newElement = parent.insert(function () { - return newElementNode; - }, function () { - return element.node(); - }); - element.remove(); - return newElement; - } - function insertElementData(element, datum) { - element.datum(datum); - element.data([datum], function (d) { - return d.key; - }); - } - function insertAllElementsData(element, datum) { - insertElementData(element, datum); - var children = d3__namespace.selectAll(element.node().childNodes); - children.each(function (d, i) { - insertAllElementsData(d3__namespace.select(this), datum.children[i]); - }); - } - function insertChildren(element, index) { - var children = element.selectAll(function () { - return element.node().childNodes; - }); - children = children.data(function (d) { - return d.children; - }, function (d) { - return d.tag + '-' + index; - }); - var childrenEnter = children.enter().append(function (d) { - return createElement(d); - }); - var childrenExit = children.exit(); - childrenExit = childrenExit.remove(); - children = childrenEnter.merge(children); - var childTagIndexes = {}; - children.each(function (childData) { - var childTag = childData.tag; - if (childTagIndexes[childTag] == null) { - childTagIndexes[childTag] = 0; - } - var childIndex = childTagIndexes[childTag]++; - attributeElement.call(this, childData, childIndex); - }); - } - function attributeElement(data) { - var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - var element = d3__namespace.select(this); - data.tag; - var attributes = data.attributes; - var currentAttributes = element.node().attributes; - if (currentAttributes) { - for (var i = 0; i < currentAttributes.length; i++) { - var currentAttribute = currentAttributes[i]; - var name = currentAttribute.name; - if (name.split(':')[0] != 'xmlns' && currentAttribute.namespaceURI) { - var namespaceURIParts = currentAttribute.namespaceURI.split('/'); - var namespace = namespaceURIParts[namespaceURIParts.length - 1]; - name = namespace + ':' + name; - } - if (!(name in attributes)) { - attributes[name] = null; - } - } - } - for (var _i2 = 0, _Object$keys2 = Object.keys(attributes); _i2 < _Object$keys2.length; _i2++) { - var attributeName = _Object$keys2[_i2]; - element.attr(attributeName, attributes[attributeName]); - } - if (data.text) { - element.text(data.text); - } - insertChildren(element, index); - } - - function zoom (enable) { - this._options.zoom = enable; - if (this._options.zoom && !this._zoomBehavior) { - createZoomBehavior.call(this); - } else if (!this._options.zoom && this._zoomBehavior) { - this._zoomSelection.on(".zoom", null); - this._zoomBehavior = null; - } - return this; - } - function createZoomBehavior() { - var graphvizInstance = this; - function zoomed(event) { - var g = d3__namespace.select(svg.node().querySelector("g")); - g.attr('transform', event.transform); - graphvizInstance._dispatch.call('zoom', graphvizInstance); - } - var root = this._selection; - var svg = d3__namespace.select(root.node().querySelector("svg")); - if (svg.size() == 0) { - return this; - } - this._zoomSelection = svg; - var zoomBehavior = d3Zoom.zoom().scaleExtent(this._options.zoomScaleExtent).translateExtent(this._options.zoomTranslateExtent).interpolate(d3Interpolate.interpolate).on("zoom", zoomed); - this._zoomBehavior = zoomBehavior; - var g = d3__namespace.select(svg.node().querySelector("g")); - svg.call(zoomBehavior); - if (!this._active) { - translateZoomBehaviorTransform.call(this, g); - } - this._originalTransform = d3Zoom.zoomTransform(svg.node()); - return this; - } - function getTranslatedZoomTransform(selection) { - // Get the current zoom transform for the top level svg and - // translate it uniformly with the given selection, using the - // difference between the translation specified in the selection's - // data and it's saved previous translation. The selection is - // normally the top level g element of the graph. - var oldTranslation = this._translation; - var oldScale = this._scale; - var newTranslation = selection.datum().translation; - var newScale = selection.datum().scale; - var t = d3Zoom.zoomTransform(this._zoomSelection.node()); - if (oldTranslation) { - t = t.scale(1 / oldScale); - t = t.translate(-oldTranslation.x, -oldTranslation.y); - } - t = t.translate(newTranslation.x, newTranslation.y); - t = t.scale(newScale); - return t; - } - function translateZoomBehaviorTransform(selection) { - // Translate the current zoom transform for the top level svg - // uniformly with the given selection, using the difference - // between the translation specified in the selection's data and - // it's saved previous translation. The selection is normally the - // top level g element of the graph. - this._zoomBehavior.transform(this._zoomSelection, getTranslatedZoomTransform.call(this, selection)); - - // Save the selections's new translation and scale. - this._translation = selection.datum().translation; - this._scale = selection.datum().scale; - - // Set the original zoom transform to the translation and scale specified in - // the selection's data. - this._originalTransform = d3Zoom.zoomIdentity.translate(selection.datum().translation.x, selection.datum().translation.y).scale(selection.datum().scale); - } - function resetZoom(transition) { - // Reset the zoom transform to the original zoom transform. - var selection = this._zoomSelection; - if (transition) { - selection = selection.transition(transition); - } - selection.call(this._zoomBehavior.transform, this._originalTransform); - return this; - } - function zoomScaleExtent(extent) { - this._options.zoomScaleExtent = extent; - return this; - } - function zoomTranslateExtent(extent) { - this._options.zoomTranslateExtent = extent; - return this; - } - function zoomBehavior() { - return this._zoomBehavior || null; - } - function zoomSelection() { - return this._zoomSelection || null; - } - - function pathTween(points, d1) { - return function () { - var pointInterpolators = points.map(function (p) { - return d3Interpolate.interpolate([p[0][0], p[0][1]], [p[1][0], p[1][1]]); - }); - return function (t) { - return t < 1 ? "M" + pointInterpolators.map(function (p) { - return p(t); - }).join("L") : d1; - }; - }; - } - function pathTweenPoints(node, d1, precision, precisionIsRelative) { - var path0 = node; - var path1 = path0.cloneNode(); - var n0 = path0.getTotalLength(); - var n1 = (path1.setAttribute("d", d1), path1).getTotalLength(); - - // Uniform sampling of distance based on specified precision. - var distances = [0]; - var i = 0; - var dt = precisionIsRelative ? precision : precision / Math.max(n0, n1); - while ((i += dt) < 1) { - distances.push(i); - } - distances.push(1); - - // Compute point-interpolators at each distance. - var points = distances.map(function (t) { - var p0 = path0.getPointAtLength(t * n0); - var p1 = path1.getPointAtLength(t * n1); - return [[p0.x, p0.y], [p1.x, p1.y]]; - }); - return points; - } - - function data () { - return this._data || null; - } - function isEdgeElementParent(datum) { - return datum.attributes["class"] == 'edge' || datum.tag == 'a' && datum.parent.tag == 'g' && datum.parent.parent.attributes["class"] == 'edge'; - } - function isEdgeElement(datum) { - return datum.parent && isEdgeElementParent(datum.parent); - } - function getEdgeGroup(datum) { - if (datum.parent.attributes["class"] == 'edge') { - return datum.parent; - } else { - // datum.parent.tag == 'g' && datum.parent.parent.tag == 'g' && datum.parent.parent.parent.attributes.class == 'edge' - return datum.parent.parent.parent; - } - } - function getEdgeTitle(datum) { - return getEdgeGroup(datum).children.find(function (e) { - return e.tag == 'title'; - }); - } - - function render (callback) { - if (this._busy) { - this._queue.push(this.render.bind(this, callback)); - return this; - } - this._dispatch.call('renderStart', this); - if (this._transitionFactory) { - d3Timer.timeout(function () { - // Decouple from time spent. See https://github.com/d3/d3-timer/issues/27 - this._transition = d3Transition.transition(this._transitionFactory()); - _render.call(this, callback); - }.bind(this), 0); - } else { - _render.call(this, callback); - } - return this; - } - function _render(callback) { - var transitionInstance = this._transition; - var fade = this._options.fade && transitionInstance != null; - var tweenPaths = this._options.tweenPaths; - var tweenShapes = this._options.tweenShapes; - var convertEqualSidedPolygons = this._options.convertEqualSidedPolygons; - var growEnteringEdges = this._options.growEnteringEdges && transitionInstance != null; - var attributer = this._attributer; - var graphvizInstance = this; - function insertChildren(element) { - var children = element.selectAll(function () { - return element.node().childNodes; - }); - children = children.data(function (d) { - return d.children; - }, function (d) { - return d.key; - }); - var childrenEnter = children.enter().append(function (d) { - var element = createElement(d); - if (d.tag == '#text' && fade) { - element.nodeValue = d.text; - } - return element; - }); - if (fade || growEnteringEdges && isEdgeElementParent(element.datum())) { - var childElementsEnter = childrenEnter.filter(function (d) { - return d.tag[0] == '#' ? null : this; - }).each(function (d) { - var childEnter = d3__namespace.select(this); - for (var _i = 0, _Object$keys = Object.keys(d.attributes); _i < _Object$keys.length; _i++) { - var attributeName = _Object$keys[_i]; - var attributeValue = d.attributes[attributeName]; - childEnter.attr(attributeName, attributeValue); - } - }); - childElementsEnter.filter(function (d) { - return d.tag == 'svg' || d.tag == 'g' ? null : this; - }).style("opacity", 0.0); - } - var childrenExit = children.exit(); - if (attributer) { - childrenExit.each(attributer); - } - if (transitionInstance) { - childrenExit = childrenExit.transition(transitionInstance); - if (fade) { - childrenExit.filter(function (d) { - return d.tag[0] == '#' ? null : this; - }).style("opacity", 0.0); - } - } - childrenExit = childrenExit.remove(); - children = childrenEnter.merge(children).order(); - children.each(attributeElement); - } - function attributeElement(data) { - var element = d3__namespace.select(this); - if (data.tag == "svg") { - var options = graphvizInstance._options; - if (options.width != null || options.height != null) { - var width = options.width; - var height = options.height; - if (width == null) { - width = data.attributes.width.replace('pt', '') * 4 / 3; - } else { - element.attr("width", width); - data.attributes.width = width; - } - if (height == null) { - height = data.attributes.height.replace('pt', '') * 4 / 3; - } else { - element.attr("height", height); - data.attributes.height = height; - } - if (!options.fit) { - element.attr("viewBox", "0 0 ".concat(width * 3 / 4 / options.scale, " ").concat(height * 3 / 4 / options.scale)); - data.attributes.viewBox = "0 0 ".concat(width * 3 / 4 / options.scale, " ").concat(height * 3 / 4 / options.scale); - } - } - if (options.scale != 1 && (options.fit || options.width == null && options.height == null)) { - width = data.attributes.viewBox.split(' ')[2]; - height = data.attributes.viewBox.split(' ')[3]; - element.attr("viewBox", "0 0 ".concat(width / options.scale, " ").concat(height / options.scale)); - data.attributes.viewBox = "0 0 ".concat(width / options.scale, " ").concat(height / options.scale); - } - } - if (attributer) { - element.each(attributer); - } - var tag = data.tag; - var attributes = data.attributes; - var currentAttributes = element.node().attributes; - if (currentAttributes) { - for (var i = 0; i < currentAttributes.length; i++) { - var currentAttribute = currentAttributes[i]; - var name = currentAttribute.name; - if (name.split(':')[0] != 'xmlns' && currentAttribute.namespaceURI) { - var namespaceURIParts = currentAttribute.namespaceURI.split('/'); - var namespace = namespaceURIParts[namespaceURIParts.length - 1]; - name = namespace + ':' + name; - } - if (!(name in attributes)) { - attributes[name] = null; - } - } - } - var convertShape = false; - var convertPrevShape = false; - if (tweenShapes && transitionInstance) { - if ((this.nodeName == 'polygon' || this.nodeName == 'ellipse') && data.alternativeOld) { - convertPrevShape = true; - } - if ((tag == 'polygon' || tag == 'ellipse') && data.alternativeNew) { - convertShape = true; - } - if (this.nodeName == 'polygon' && tag == 'polygon' && data.alternativeOld) { - var prevData = extractElementData(element); - var prevPoints = prevData.attributes.points; - if (!convertEqualSidedPolygons) { - var nPrevPoints = prevPoints.split(' ').length; - var points = data.attributes.points; - var nPoints = points.split(' ').length; - if (nPoints == nPrevPoints) { - convertShape = false; - convertPrevShape = false; - } - } - } - if (convertPrevShape) { - var prevPathData = data.alternativeOld; - var pathElement = replaceElement(element, prevPathData); - pathElement.data([data], function () { - return data.key; - }); - element = pathElement; - } - if (convertShape) { - var newPathData = data.alternativeNew; - tag = 'path'; - attributes = newPathData.attributes; - } - } - var elementTransition = element; - if (transitionInstance) { - elementTransition = elementTransition.transition(transitionInstance); - if (fade) { - elementTransition.filter(function (d) { - return d.tag[0] == '#' ? null : this; - }).style("opacity", 1.0); - } - elementTransition.filter(function (d) { - return d.tag[0] == '#' ? null : this; - }).on("end", function (d) { - d3__namespace.select(this).attr('style', d && d.attributes && d.attributes.style || null); - }); - } - var growThisPath = growEnteringEdges && tag == 'path' && data.offset; - if (growThisPath) { - var totalLength = data.totalLength; - element.attr("stroke-dasharray", totalLength + " " + totalLength).attr("stroke-dashoffset", totalLength).attr('transform', 'translate(' + data.offset.x + ',' + data.offset.y + ')'); - attributes["stroke-dashoffset"] = 0; - attributes['transform'] = 'translate(0,0)'; - elementTransition.attr("stroke-dashoffset", attributes["stroke-dashoffset"]).attr('transform', attributes['transform']).on("start", function () { - d3__namespace.select(this).style('opacity', null); - }).on("end", function () { - d3__namespace.select(this).attr('stroke-dashoffset', null).attr('stroke-dasharray', null).attr('transform', null); - }); - } - var moveThisPolygon = growEnteringEdges && tag == 'polygon' && isEdgeElement(data) && data.offset && data.parent.children[3].tag == 'path'; - if (moveThisPolygon) { - var edgePath = d3__namespace.select(element.node().parentNode.querySelector("path")); - var p0 = edgePath.node().getPointAtLength(0); - var p1 = edgePath.node().getPointAtLength(data.totalLength); - var p2 = edgePath.node().getPointAtLength(data.totalLength - 1); - var angle1 = Math.atan2(p1.y - p2.y, p1.x - p2.x) * 180 / Math.PI; - var x = p0.x - p1.x + data.offset.x; - var y = p0.y - p1.y + data.offset.y; - element.attr('transform', 'translate(' + x + ',' + y + ')'); - elementTransition.attrTween("transform", function () { - return function (t) { - var p = edgePath.node().getPointAtLength(data.totalLength * t); - var p2 = edgePath.node().getPointAtLength(data.totalLength * t + 1); - var angle = Math.atan2(p2.y - p.y, p2.x - p.x) * 180 / Math.PI - angle1; - x = p.x - p1.x + data.offset.x * (1 - t); - y = p.y - p1.y + data.offset.y * (1 - t); - return 'translate(' + x + ',' + y + ') rotate(' + angle + ' ' + p1.x + ' ' + p1.y + ')'; - }; - }).on("start", function () { - d3__namespace.select(this).style('opacity', null); - }).on("end", function () { - d3__namespace.select(this).attr('transform', null); - }); - } - var tweenThisPath = tweenPaths && transitionInstance && tag == 'path' && element.attr('d') != null; - for (var _i2 = 0, _Object$keys2 = Object.keys(attributes); _i2 < _Object$keys2.length; _i2++) { - var attributeName = _Object$keys2[_i2]; - var attributeValue = attributes[attributeName]; - if (tweenThisPath && attributeName == 'd') { - var points = (data.alternativeOld || data).points; - if (points) { - elementTransition.attrTween("d", pathTween(points, attributeValue)); - } - } else { - if (attributeName == 'transform' && data.translation) { - if (transitionInstance) { - var onEnd = elementTransition.on("end"); - elementTransition.on("start", function () { - if (graphvizInstance._zoomBehavior) { - // Update the transform to transition to, just before the transition starts - // in order to catch changes between the transition scheduling to its start. - elementTransition.tween("attr.transform", function () { - var node = this; - return function (t) { - node.setAttribute("transform", d3Interpolate.interpolateTransformSvg(d3Zoom.zoomTransform(graphvizInstance._zoomSelection.node()).toString(), getTranslatedZoomTransform.call(graphvizInstance, element).toString())(t)); - }; - }); - } - }).on("end", function () { - onEnd.call(this); - // Update the zoom transform to the new translated transform - if (graphvizInstance._zoomBehavior) { - translateZoomBehaviorTransform.call(graphvizInstance, element); - } - }); - } else { - if (graphvizInstance._zoomBehavior) { - // Update the transform attribute to set with the current pan translation - translateZoomBehaviorTransform.call(graphvizInstance, element); - attributeValue = getTranslatedZoomTransform.call(graphvizInstance, element).toString(); - } - } - } - elementTransition.attr(attributeName, attributeValue); - } - } - if (convertShape) { - elementTransition.on("end", function (d, i, nodes) { - pathElement = d3__namespace.select(this); - var newElement = replaceElement(pathElement, d); - newElement.data([d], function () { - return d.key; - }); - }); - } - if (data.text) { - elementTransition.text(data.text); - } - insertChildren(element); - } - var root = this._selection; - if (transitionInstance != null) { - // Ensure original SVG shape elements are restored after transition before rendering new graph - var jobs = this._jobs; - if (graphvizInstance._active) { - jobs.push(null); - return this; - } else { - root.transition(transitionInstance).transition().duration(0).on("end", function () { - graphvizInstance._active = false; - if (jobs.length != 0) { - jobs.shift(); - graphvizInstance.render(); - } - }); - this._active = true; - } - } - if (transitionInstance != null) { - root.transition(transitionInstance).on("start", function () { - graphvizInstance._dispatch.call('transitionStart', graphvizInstance); - }).on("end", function () { - graphvizInstance._dispatch.call('transitionEnd', graphvizInstance); - }).transition().duration(0).on("start", function () { - graphvizInstance._dispatch.call('restoreEnd', graphvizInstance); - graphvizInstance._dispatch.call('end', graphvizInstance); - if (callback) { - callback.call(graphvizInstance); - } - }); - } - var data = this._data; - var svg = root.selectAll("svg").data([data], function (d) { - return d.key; - }); - svg = svg.enter().append("svg").merge(svg); - attributeElement.call(svg.node(), data); - if (this._options.zoom && !this._zoomBehavior) { - createZoomBehavior.call(this); - } - graphvizInstance._dispatch.call('renderEnd', graphvizInstance); - if (transitionInstance == null) { - this._dispatch.call('end', this); - if (callback) { - callback.call(this); - } - } - return this; - } - - function graphvizVersion () { - return this._graphvizVersion; - } - - var io=ArrayBuffer,H=Uint8Array,Qe=Uint16Array,so=Int16Array,qe=Int32Array,pr=function(p,c,d){if(H.prototype.slice)return H.prototype.slice.call(p,c,d);(c==null||c<0)&&(c=0),(d==null||d>p.length)&&(d=p.length);var s=new H(d-c);return s.set(p.subarray(c,d)),s},Ye=function(p,c,d,s){if(H.prototype.fill)return H.prototype.fill.call(p,c,d,s);for((d==null||d<0)&&(d=0),(s==null||s>p.length)&&(s=p.length);dp.length)&&(s=p.length);d2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],T=function(p,c,d){var s=new Error(c||po[p]);if(s.code=p,Error.captureStackTrace&&Error.captureStackTrace(s,T),!d)throw s;return s},jr=function(p,c,d){for(var s=0,f=0;s>>0},co=function(p,c){var d=p[0]|p[1]<<8|p[2]<<16;if(d==3126568&&p[3]==253){var s=p[4],f=s>>5&1,m=s>>2&1,_=s&3,h=s>>6;s&8&&T(0);var M=6-f,I=_==3?4:_,Q=jr(p,M,I);M+=I;var Z=h?1<>3);z=q+(q>>3)*(p[5]&7);}z>2145386496&&T(1);var X=new H((c==1?Y||z:c?0:z)+12);return X[0]=1,X[4]=4,X[8]=8,{b:M+Z,y:0,l:0,d:Q,w:c&&c!=1?c:X.subarray(12),e:z,o:new qe(X.buffer,0,3),u:Y,c:m,m:Math.min(131072,z)}}else if((d>>4|p[3]<<20)==25481893)return lo(p,4)+8;T(0);},Xe=function(p){for(var c=0;1<d&&T(3);for(var m=1<0;){var S=Xe(_+1),P=s>>3,ae=(1<>(s&7)&ae,w=(1<w&&(J-=j)),Y[++h]=--J,J==-1?(_+=J,V[--Q]=h):_-=J,!J)do{var ie=s>>3;M=(p[ie]|p[ie+1]<<8)>>(s&7)&3,s+=2,h+=M;}while(M==3)}(h>255||_)&&T(0);for(var b=0,k=(m>>1)+(m>>3)+3,se=m-1,oe=0;oe<=h;++oe){var K=Y[oe];if(K<1){z[oe]=-K;continue}for(I=0;I=Q)}}for(b&&T(0),I=0;I>3,{b:f,s:V,n:re,t:q}]},fo=function(p,c){var d=0,s=-1,f=new H(292),m=p[c],_=f.subarray(0,256),h=f.subarray(256,268),M=new Qe(f.buffer,268);if(m<128){var I=je(p,c+1,6),Q=I[0],Z=I[1];c+=m;var Y=Q<<3,z=p[c];z||T(0);for(var q=0,X=0,V=Z.b,re=V,S=(++c<<3)-8+Xe(z);S-=V,!(S>3;if(q+=(p[P]|p[P+1]<<8)>>(S&7)&(1<>3,X+=(p[P]|p[P+1]<<8)>>(S&7)&(1<255&&T(0);}else {for(s=m-127;d>4,_[d+1]=ae&15;}++c;}var J=0;for(d=0;d11&&T(0),J+=w&&1<0;--d){var oe=M[d];Ye(se,d,oe,M[d-1]=oe+h[d]*(1<h&&Z>3,z=(p[Y]|p[Y+1]<<8|p[Y+2]<<16)>>(Q&7);M=(M<>2,_=m<<1,h=m+_;Ze(p.subarray(s,s+=p[0]|p[1]<<8),c.subarray(0,m),d),Ze(p.subarray(s,s+=p[2]|p[3]<<8),c.subarray(m,_),d),Ze(p.subarray(s,s+=p[4]|p[5]<<8),c.subarray(_,h),d),Ze(p.subarray(s),c.subarray(h),d);},yo=function(p,c,d){var s,f=c.b,m=p[f],_=m>>1&3;c.l=m&1;var h=m>>3|p[f+1]<<5|p[f+2]<<13,M=(f+=3)+h;if(_==1)return f>=p.length?void 0:(c.b=f+1,d?(Ye(d,p[f],c.y,c.y+=h),d):Ye(new H(h),p[f]));if(!(M>p.length)){if(_==0)return c.b=M,d?(d.set(p.subarray(f,M),c.y),c.y+=h,d):pr(p,f,M);if(_==2){var I=p[f],Q=I&3,Z=I>>2&3,Y=I>>4,z=0,q=0;Q<2?Z&1?Y|=p[++f]<<4|(Z&2&&p[++f]<<12):Y=I>>3:(q=Z,Z<2?(Y|=(p[++f]&63)<<4,z=p[f]>>6|p[++f]<<2):Z==2?(Y|=p[++f]<<4|(p[++f]&3)<<12,z=p[f]>>2|p[++f]<<6):(Y|=p[++f]<<4|(p[++f]&63)<<12,z=p[f]>>6|p[++f]<<2|p[++f]<<10)),++f;var X=d?d.subarray(c.y,c.y+c.m):new H(c.m),V=X.length-Y;if(Q==0)X.set(p.subarray(f,f+=Y),V);else if(Q==1)Ye(X,p[f++],V);else {var re=c.h;if(Q==2){var S=fo(p,f);z+=f-(f=S[0]),c.h=re=S[1];}else re||T(0);(q?wo:Ze)(p.subarray(f,f+=z),X.subarray(V),re);}var P=p[f++];if(P){P==255?P=(p[f++]|p[f++]<<8)+32512:P>127&&(P=P-128<<8|p[f++]);var ae=p[f++];ae&3&&T(0);for(var J=[mo,_o,vo],w=2;w>-1;--w){var j=ae>>(w<<1)+2&3;if(j==1){var W=new H([0,0,p[f++]]);J[w]={s:W.subarray(2,3),n:W.subarray(0,1),t:new Qe(W.buffer,0,1),b:0};}else j==2?(s=je(p,f,9-(w&1)),f=s[0],J[w]=s[1]):j==3&&(c.t||T(0),J[w]=c.t[w]);}var ie=c.t=J,b=ie[0],k=ie[1],se=ie[2],oe=p[M-1];oe||T(0);var K=(M<<3)-8+Xe(oe)-se.b,L=K>>3,A=0,Le=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var Be=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var Re=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var $e=1<>>(K&7)&$e-1);L=(K-=dr[Ve])>>3;var me=ho[Ve]+((p[L]|p[L+1]<<8|p[L+2]<<16)>>(K&7)&(1<>3;var _e=go[Ne]+((p[L]|p[L+1]<<8|p[L+2]<<16)>>(K&7)&(1<>3,Le=se.t[Le]+((p[L]|p[L+1]<<8)>>(K&7)&(1<>3,Re=b.t[Re]+((p[L]|p[L+1]<<8)>>(K&7)&(1<>3,Be=k.t[Be]+((p[L]|p[L+1]<<8)>>(K&7)&(1<3)c.o[2]=c.o[1],c.o[1]=c.o[0],c.o[0]=ce-=3;else {var Ge=ce-(_e!=0);Ge?(ce=Ge==3?c.o[0]-1:c.o[Ge],Ge>1&&(c.o[2]=c.o[1]),c.o[1]=c.o[0],c.o[0]=ce):ce=c.o[0];}for(var w=0;w<_e;++w)X[A+w]=X[V+w];A+=_e,V+=_e;var Ee=A-ce;if(Ee<0){var De=-Ee,We=c.e+Ee;De>me&&(De=me);for(var w=0;w{typeof document<"u"?document.currentScript?.src:void 0;return function(c={}){var d,s=c,f,m,_=new Promise((e,r)=>{f=e,m=r;}),M=Object.assign({},s),I="./this.program",Q=(e,r)=>{throw r},Z="";function Y(e){return Z+e}var z,q,X=console.log.bind(console),V=console.error.bind(console);Object.assign(s,M),M=null;var re;s.wasmBinary&&(re=s.wasmBinary);var S,P=!1;function J(e,r){e||_e(r);}var w,j,W,b,k,oe;function K(){var e=S.buffer;s.HEAP8=w=new Int8Array(e),s.HEAP16=W=new Int16Array(e),s.HEAPU8=j=new Uint8Array(e),s.HEAPU16=new Uint16Array(e),s.HEAP32=b=new Int32Array(e),s.HEAPU32=k=new Uint32Array(e),s.HEAPF32=new Float32Array(e),s.HEAPF64=oe=new Float64Array(e);}var L=[],A=[],Le=[];function Re(){Ae(L);}function Ne(){!s.noFSInit&&!o.init.initialized&&o.init(),o.ignorePermissions=!1,Ae(A);}function Pe(){Ae(Le);}function Ve(e){A.unshift(e);}var ye=0,be=null;function ce(e){ye++;}function me(e){if(ye--,ye==0&&(be)){var r=be;be=null,r();}}function _e(e){e="Aborted("+e+")",V(e),P=!0,e+=". Build with -sASSERTIONS for more info.";var r=new WebAssembly.RuntimeError(e);throw m(r),r}var Ge="data:application/octet-stream;base64,",Ee=e=>e.startsWith(Ge),De=e=>e.startsWith("file://");function We(){var e="graphvizlib.wasm";return Ee(e)?e:Y(e)}var Je;function fr(e){if(e==Je&&re)return new Uint8Array(re);throw "both async and sync fetching of the wasm failed"}function Jr(e){return re?Promise.resolve().then(()=>fr(e)):z(e).then(r=>new Uint8Array(r),()=>fr(e))}function vr(e,r,t){return Jr(e).then(n=>WebAssembly.instantiate(n,r)).then(t,n=>{V(`failed to asynchronously prepare wasm: ${n}`),_e(n);})}function Sr(e,r,t,n){return !e&&typeof WebAssembly.instantiateStreaming=="function"&&!Ee(r)&&!De(r)&&typeof fetch=="function"?fetch(r,{credentials:"same-origin"}).then(a=>{var i=WebAssembly.instantiateStreaming(a,t);return i.then(n,function(u){return V(`wasm streaming compile failed: ${u}`),V("falling back to ArrayBuffer instantiation"),vr(r,t,n)})}):vr(r,t,n)}function Hr(){return {a:Qt}}function Tr(){var e=Hr();function r(n,a){return N=n.exports,S=N.B,K(),Ve(N.C),me(),N}ce();function t(n){r(n.instance);}return Je||(Je=We()),Sr(re,Je,e,t).catch(m),{}}var R,$,Qr={172808:(e,r)=>{var t=ue(e),n=ue(r);o.createPath("/",C.dirname(t)),o.writeFile(C.join("/",t),n);}};function qr(e){this.name="ExitStatus",this.message=`Program terminated with exit(${e})`,this.status=e;}var Ae=e=>{for(;e.length>0;)e.shift()(s);};class $r{constructor(r){this.excPtr=r,this.ptr=r-24;}set_type(r){k[this.ptr+4>>2]=r;}get_type(){return k[this.ptr+4>>2]}set_destructor(r){k[this.ptr+8>>2]=r;}get_destructor(){return k[this.ptr+8>>2]}set_caught(r){r=r?1:0,w[this.ptr+12]=r;}get_caught(){return w[this.ptr+12]!=0}set_rethrown(r){r=r?1:0,w[this.ptr+13]=r;}get_rethrown(){return w[this.ptr+13]!=0}init(r,t){this.set_adjusted_ptr(0),this.set_type(r),this.set_destructor(t);}set_adjusted_ptr(r){k[this.ptr+16>>2]=r;}get_adjusted_ptr(){return k[this.ptr+16>>2]}get_exception_ptr(){var r=Yr(this.get_type());if(r)return k[this.excPtr>>2];var t=this.get_adjusted_ptr();return t!==0?t:this.excPtr}}var mr=0,Ar=(e,r,t)=>{var n=new $r(e);throw n.init(r,t),mr=e,mr},C={isAbs:e=>e.charAt(0)==="/",splitPath:e=>{var r=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return r.exec(e).slice(1)},normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var a=e[n];a==="."?e.splice(n,1):a===".."?(e.splice(n,1),t++):t&&(e.splice(n,1),t--);}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=C.isAbs(e),t=e.substr(-1)==="/";return e=C.normalizeArray(e.split("/").filter(n=>!!n),!r).join("/"),!e&&!r&&(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=C.splitPath(e),t=r[0],n=r[1];return !t&&!n?".":(n&&(n=n.substr(0,n.length-1)),t+n)},basename:e=>{if(e==="/")return "/";e=C.normalize(e),e=e.replace(/\/$/,"");var r=e.lastIndexOf("/");return r===-1?e:e.substr(r+1)},join:(...e)=>C.normalize(e.join("/")),join2:(e,r)=>C.normalize(e+"/"+r)},et=()=>{if(typeof crypto=="object"&&typeof crypto.getRandomValues=="function")return e=>crypto.getRandomValues(e);_e("initRandomDevice");},_r=e=>(_r=et())(e),he={resolve:(...e)=>{for(var r="",t=!1,n=e.length-1;n>=-1&&!t;n--){var a=n>=0?e[n]:o.cwd();if(typeof a!="string")throw new TypeError("Arguments to path.resolve must be strings");if(!a)return "";r=a+"/"+r,t=C.isAbs(a);}return r=C.normalizeArray(r.split("/").filter(i=>!!i),!t).join("/"),(t?"/":"")+r||"."},relative:(e,r)=>{e=he.resolve(e).substr(1),r=he.resolve(r).substr(1);function t(E){for(var G=0;G=0&&E[x]==="";x--);return G>x?[]:E.slice(G,x-G+1)}for(var n=t(e.split("/")),a=t(r.split("/")),i=Math.min(n.length,a.length),u=i,l=0;l{for(var n=r+t,a=r;e[a]&&!(a>=n);)++a;if(a-r>16&&e.buffer&&gr)return gr.decode(e.subarray(r,a));for(var i="";r>10,56320|E&1023);}}return i},er=[],rr=e=>{for(var r=0,t=0;t=55296&&n<=57343?(r+=4,++t):r+=3;}return r},tr=(e,r,t,n)=>{if(!(n>0))return 0;for(var a=t,i=t+n-1,u=0;u=55296&&l<=57343){var v=e.charCodeAt(++u);l=65536+((l&1023)<<10)|v&1023;}if(l<=127){if(t>=i)break;r[t++]=l;}else if(l<=2047){if(t+1>=i)break;r[t++]=192|l>>6,r[t++]=128|l&63;}else if(l<=65535){if(t+2>=i)break;r[t++]=224|l>>12,r[t++]=128|l>>6&63,r[t++]=128|l&63;}else {if(t+3>=i)break;r[t++]=240|l>>18,r[t++]=128|l>>12&63,r[t++]=128|l>>6&63,r[t++]=128|l&63;}}return r[t]=0,t-a};function or(e,r,t){var n=rr(e)+1,a=new Array(n),i=tr(e,a,0,a.length);return r&&(a.length=i),a}var rt=()=>{if(!er.length){var e=null;if(!e)return null;er=or(e,!0);}return er.shift()},Me={ttys:[],init(){},shutdown(){},register(e,r){Me.ttys[e]={input:[],output:[],ops:r},o.registerDevice(e,Me.stream_ops);},stream_ops:{open(e){var r=Me.ttys[e.node.rdev];if(!r)throw new o.ErrnoError(43);e.tty=r,e.seekable=!1;},close(e){e.tty.ops.fsync(e.tty);},fsync(e){e.tty.ops.fsync(e.tty);},read(e,r,t,n,a){if(!e.tty||!e.tty.ops.get_char)throw new o.ErrnoError(60);for(var i=0,u=0;u0&&(X(Fe(e.output,0)),e.output=[]);},ioctl_tcgets(e){return {c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(e,r,t){return 0},ioctl_tiocgwinsz(e){return [24,80]}},default_tty1_ops:{put_char(e,r){r===null||r===10?(V(Fe(e.output,0)),e.output=[]):r!=0&&e.output.push(r);},fsync(e){e.output&&e.output.length>0&&(V(Fe(e.output,0)),e.output=[]);}}},tt=(e,r)=>(j.fill(0,e,e+r),e),hr=(e,r)=>Math.ceil(e/r)*r,wr=e=>{e=hr(e,65536);var r=Zr(65536,e);return r?tt(r,e):0},D={ops_table:null,mount(e){return D.createNode(null,"/",16895,0)},createNode(e,r,t,n){if(o.isBlkdev(t)||o.isFIFO(t))throw new o.ErrnoError(63);D.ops_table||={dir:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr,lookup:D.node_ops.lookup,mknod:D.node_ops.mknod,rename:D.node_ops.rename,unlink:D.node_ops.unlink,rmdir:D.node_ops.rmdir,readdir:D.node_ops.readdir,symlink:D.node_ops.symlink},stream:{llseek:D.stream_ops.llseek}},file:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr},stream:{llseek:D.stream_ops.llseek,read:D.stream_ops.read,write:D.stream_ops.write,allocate:D.stream_ops.allocate,mmap:D.stream_ops.mmap,msync:D.stream_ops.msync}},link:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr,readlink:D.node_ops.readlink},stream:{}},chrdev:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr},stream:o.chrdev_stream_ops}};var a=o.createNode(e,r,t,n);return o.isDir(a.mode)?(a.node_ops=D.ops_table.dir.node,a.stream_ops=D.ops_table.dir.stream,a.contents={}):o.isFile(a.mode)?(a.node_ops=D.ops_table.file.node,a.stream_ops=D.ops_table.file.stream,a.usedBytes=0,a.contents=null):o.isLink(a.mode)?(a.node_ops=D.ops_table.link.node,a.stream_ops=D.ops_table.link.stream):o.isChrdev(a.mode)&&(a.node_ops=D.ops_table.chrdev.node,a.stream_ops=D.ops_table.chrdev.stream),a.timestamp=Date.now(),e&&(e.contents[r]=a,e.timestamp=a.timestamp),a},getFileDataAsTypedArray(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){var n=1048576;r=Math.max(r,t*(t>>0),t!=0&&(r=Math.max(r,256));var a=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(a.subarray(0,e.usedBytes),0);}},resizeFileStorage(e,r){if(e.usedBytes!=r)if(r==0)e.contents=null,e.usedBytes=0;else {var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r;}},node_ops:{getattr(e){var r={};return r.dev=o.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,o.isDir(e.mode)?r.size=4096:o.isFile(e.mode)?r.size=e.usedBytes:o.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr(e,r){r.mode!==void 0&&(e.mode=r.mode),r.timestamp!==void 0&&(e.timestamp=r.timestamp),r.size!==void 0&&D.resizeFileStorage(e,r.size);},lookup(e,r){throw o.genericErrors[44]},mknod(e,r,t,n){return D.createNode(e,r,t,n)},rename(e,r,t){if(o.isDir(e.mode)){var n;try{n=o.lookupNode(r,t);}catch{}if(n)for(var a in n.contents)throw new o.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp;},unlink(e,r){delete e.contents[r],e.timestamp=Date.now();},rmdir(e,r){var t=o.lookupNode(e,r);for(var n in t.contents)throw new o.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now();},readdir(e){var r=[".",".."];for(var t of Object.keys(e.contents))r.push(t);return r},symlink(e,r,t){var n=D.createNode(e,r,41471,0);return n.link=t,n},readlink(e){if(!o.isLink(e.mode))throw new o.ErrnoError(28);return e.link}},stream_ops:{read(e,r,t,n,a){var i=e.node.contents;if(a>=e.node.usedBytes)return 0;var u=Math.min(e.node.usedBytes-a,n);if(u>8&&i.subarray)r.set(i.subarray(a,a+u),t);else for(var l=0;l0||t+r{var a=`al ${e}`;z(e).then(i=>{r(new Uint8Array(i)),a&&me();},i=>{if(t)t();else throw `Loading data file "${e}" failed.`}),a&&ce();},nt=(e,r,t,n,a,i)=>{o.createDataFile(e,r,t,n,a,i);},at=[],it=(e,r,t,n)=>{typeof Browser<"u"&&Browser.init();var a=!1;return at.forEach(i=>{a||i.canHandle(r)&&(i.handle(e,r,t,n),a=!0);}),a},st=(e,r,t,n,a,i,u,l,v,E)=>{var G=r?he.resolve(C.join2(e,r)):e;function B(y){function O(te){E?.(),l||nt(e,r,te,n,a,v),i?.(),me();}it(y,G,O,()=>{u?.(),me();})||O(y);}ce(),typeof t=="string"?ot(t,B,u):B(t);},ut=e=>{var r={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},t=r[e];if(typeof t>"u")throw new Error(`Unknown file open mode: ${e}`);return t},nr=(e,r)=>{var t=0;return e&&(t|=365),r&&(t|=146),t},o={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:!1,ignorePermissions:!0,ErrnoError:class{constructor(e){this.name="ErrnoError",this.errno=e;}},genericErrors:{},filesystems:null,syncFSRequests:0,FSStream:class{constructor(){this.shared={};}get object(){return this.node}set object(e){this.node=e;}get isRead(){return (this.flags&2097155)!==1}get isWrite(){return (this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(e){this.shared.flags=e;}get position(){return this.shared.position}set position(e){this.shared.position=e;}},FSNode:class{constructor(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=o.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n,this.readMode=365,this.writeMode=146;}get read(){return (this.mode&this.readMode)===this.readMode}set read(e){e?this.mode|=this.readMode:this.mode&=~this.readMode;}get write(){return (this.mode&this.writeMode)===this.writeMode}set write(e){e?this.mode|=this.writeMode:this.mode&=~this.writeMode;}get isFolder(){return o.isDir(this.mode)}get isDevice(){return o.isChrdev(this.mode)}},lookupPath(e,r={}){if(e=he.resolve(e),!e)return {path:"",node:null};var t={follow_mount:!0,recurse_count:0};if(r=Object.assign(t,r),r.recurse_count>8)throw new o.ErrnoError(32);for(var n=e.split("/").filter(x=>!!x),a=o.root,i="/",u=0;u40)throw new o.ErrnoError(32)}}return {path:i,node:a}},getPath(e){for(var r;;){if(o.isRoot(e)){var t=e.mount.mountpoint;return r?t[t.length-1]!=="/"?`${t}/${r}`:t+r:t}r=r?`${e.name}/${r}`:e.name,e=e.parent;}},hashName(e,r){for(var t=0,n=0;n>>0)%o.nameTable.length},hashAddNode(e){var r=o.hashName(e.parent.id,e.name);e.name_next=o.nameTable[r],o.nameTable[r]=e;},hashRemoveNode(e){var r=o.hashName(e.parent.id,e.name);if(o.nameTable[r]===e)o.nameTable[r]=e.name_next;else for(var t=o.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next;}},lookupNode(e,r){var t=o.mayLookup(e);if(t)throw new o.ErrnoError(t);for(var n=o.hashName(e.id,r),a=o.nameTable[n];a;a=a.name_next){var i=a.name;if(a.parent.id===e.id&&i===r)return a}return o.lookup(e,r)},createNode(e,r,t,n){var a=new o.FSNode(e,r,t,n);return o.hashAddNode(a),a},destroyNode(e){o.hashRemoveNode(e);},isRoot(e){return e===e.parent},isMountpoint(e){return !!e.mounted},isFile(e){return (e&61440)===32768},isDir(e){return (e&61440)===16384},isLink(e){return (e&61440)===40960},isChrdev(e){return (e&61440)===8192},isBlkdev(e){return (e&61440)===24576},isFIFO(e){return (e&61440)===4096},isSocket(e){return (e&49152)===49152},flagsToPermissionString(e){var r=["r","w","rw"][e&3];return e&512&&(r+="w"),r},nodePermissions(e,r){return o.ignorePermissions?0:r.includes("r")&&!(e.mode&292)||r.includes("w")&&!(e.mode&146)||r.includes("x")&&!(e.mode&73)?2:0},mayLookup(e){if(!o.isDir(e.mode))return 54;var r=o.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate(e,r){try{var t=o.lookupNode(e,r);return 20}catch{}return o.nodePermissions(e,"wx")},mayDelete(e,r,t){var n;try{n=o.lookupNode(e,r);}catch(i){return i.errno}var a=o.nodePermissions(e,"wx");if(a)return a;if(t){if(!o.isDir(n.mode))return 54;if(o.isRoot(n)||o.getPath(n)===o.cwd())return 10}else if(o.isDir(n.mode))return 31;return 0},mayOpen(e,r){return e?o.isLink(e.mode)?32:o.isDir(e.mode)&&(o.flagsToPermissionString(r)!=="r"||r&512)?31:o.nodePermissions(e,o.flagsToPermissionString(r)):44},MAX_OPEN_FDS:4096,nextfd(){for(var e=0;e<=o.MAX_OPEN_FDS;e++)if(!o.streams[e])return e;throw new o.ErrnoError(33)},getStreamChecked(e){var r=o.getStream(e);if(!r)throw new o.ErrnoError(8);return r},getStream:e=>o.streams[e],createStream(e,r=-1){return e=Object.assign(new o.FSStream,e),r==-1&&(r=o.nextfd()),e.fd=r,o.streams[r]=e,e},closeStream(e){o.streams[e]=null;},dupStream(e,r=-1){var t=o.createStream(e,r);return t.stream_ops?.dup?.(t),t},chrdev_stream_ops:{open(e){var r=o.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open?.(e);},llseek(){throw new o.ErrnoError(70)}},major:e=>e>>8,minor:e=>e&255,makedev:(e,r)=>e<<8|r,registerDevice(e,r){o.devices[e]={stream_ops:r};},getDevice:e=>o.devices[e],getMounts(e){for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push(...n.mounts);}return r},syncfs(e,r){typeof e=="function"&&(r=e,e=!1),o.syncFSRequests++,o.syncFSRequests>1&&V(`warning: ${o.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`);var t=o.getMounts(o.root.mount),n=0;function a(u){return o.syncFSRequests--,r(u)}function i(u){if(u)return i.errored?void 0:(i.errored=!0,a(u));++n>=t.length&&a(null);}t.forEach(u=>{if(!u.type.syncfs)return i(null);u.type.syncfs(u,e,i);});},mount(e,r,t){var n=t==="/",a=!t,i;if(n&&o.root)throw new o.ErrnoError(10);if(!n&&!a){var u=o.lookupPath(t,{follow_mount:!1});if(t=u.path,i=u.node,o.isMountpoint(i))throw new o.ErrnoError(10);if(!o.isDir(i.mode))throw new o.ErrnoError(54)}var l={type:e,opts:r,mountpoint:t,mounts:[]},v=e.mount(l);return v.mount=l,l.root=v,n?o.root=v:i&&(i.mounted=l,i.mount&&i.mount.mounts.push(l)),v},unmount(e){var r=o.lookupPath(e,{follow_mount:!1});if(!o.isMountpoint(r.node))throw new o.ErrnoError(28);var t=r.node,n=t.mounted,a=o.getMounts(n);Object.keys(o.nameTable).forEach(u=>{for(var l=o.nameTable[u];l;){var v=l.name_next;a.includes(l.mount)&&o.destroyNode(l),l=v;}}),t.mounted=null;var i=t.mount.mounts.indexOf(n);t.mount.mounts.splice(i,1);},lookup(e,r){return e.node_ops.lookup(e,r)},mknod(e,r,t){var n=o.lookupPath(e,{parent:!0}),a=n.node,i=C.basename(e);if(!i||i==="."||i==="..")throw new o.ErrnoError(28);var u=o.mayCreate(a,i);if(u)throw new o.ErrnoError(u);if(!a.node_ops.mknod)throw new o.ErrnoError(63);return a.node_ops.mknod(a,i,r,t)},create(e,r){return r=r!==void 0?r:438,r&=4095,r|=32768,o.mknod(e,r,0)},mkdir(e,r){return r=r!==void 0?r:511,r&=1023,r|=16384,o.mknod(e,r,0)},mkdirTree(e,r){for(var t=e.split("/"),n="",a=0;a"u"&&(t=r,r=438),r|=8192,o.mknod(e,r,t)},symlink(e,r){if(!he.resolve(e))throw new o.ErrnoError(44);var t=o.lookupPath(r,{parent:!0}),n=t.node;if(!n)throw new o.ErrnoError(44);var a=C.basename(r),i=o.mayCreate(n,a);if(i)throw new o.ErrnoError(i);if(!n.node_ops.symlink)throw new o.ErrnoError(63);return n.node_ops.symlink(n,a,e)},rename(e,r){var t=C.dirname(e),n=C.dirname(r),a=C.basename(e),i=C.basename(r),u,l,v;if(u=o.lookupPath(e,{parent:!0}),l=u.node,u=o.lookupPath(r,{parent:!0}),v=u.node,!l||!v)throw new o.ErrnoError(44);if(l.mount!==v.mount)throw new o.ErrnoError(75);var E=o.lookupNode(l,a),G=he.relative(e,n);if(G.charAt(0)!==".")throw new o.ErrnoError(28);if(G=he.relative(r,t),G.charAt(0)!==".")throw new o.ErrnoError(55);var x;try{x=o.lookupNode(v,i);}catch{}if(E!==x){var B=o.isDir(E.mode),y=o.mayDelete(l,a,B);if(y)throw new o.ErrnoError(y);if(y=x?o.mayDelete(v,i,B):o.mayCreate(v,i),y)throw new o.ErrnoError(y);if(!l.node_ops.rename)throw new o.ErrnoError(63);if(o.isMountpoint(E)||x&&o.isMountpoint(x))throw new o.ErrnoError(10);if(v!==l&&(y=o.nodePermissions(l,"w"),y))throw new o.ErrnoError(y);o.hashRemoveNode(E);try{l.node_ops.rename(E,v,i),E.parent=v;}catch(O){throw O}finally{o.hashAddNode(E);}}},rmdir(e){var r=o.lookupPath(e,{parent:!0}),t=r.node,n=C.basename(e),a=o.lookupNode(t,n),i=o.mayDelete(t,n,!0);if(i)throw new o.ErrnoError(i);if(!t.node_ops.rmdir)throw new o.ErrnoError(63);if(o.isMountpoint(a))throw new o.ErrnoError(10);t.node_ops.rmdir(t,n),o.destroyNode(a);},readdir(e){var r=o.lookupPath(e,{follow:!0}),t=r.node;if(!t.node_ops.readdir)throw new o.ErrnoError(54);return t.node_ops.readdir(t)},unlink(e){var r=o.lookupPath(e,{parent:!0}),t=r.node;if(!t)throw new o.ErrnoError(44);var n=C.basename(e),a=o.lookupNode(t,n),i=o.mayDelete(t,n,!1);if(i)throw new o.ErrnoError(i);if(!t.node_ops.unlink)throw new o.ErrnoError(63);if(o.isMountpoint(a))throw new o.ErrnoError(10);t.node_ops.unlink(t,n),o.destroyNode(a);},readlink(e){var r=o.lookupPath(e),t=r.node;if(!t)throw new o.ErrnoError(44);if(!t.node_ops.readlink)throw new o.ErrnoError(28);return he.resolve(o.getPath(t.parent),t.node_ops.readlink(t))},stat(e,r){var t=o.lookupPath(e,{follow:!r}),n=t.node;if(!n)throw new o.ErrnoError(44);if(!n.node_ops.getattr)throw new o.ErrnoError(63);return n.node_ops.getattr(n)},lstat(e){return o.stat(e,!0)},chmod(e,r,t){var n;if(typeof e=="string"){var a=o.lookupPath(e,{follow:!t});n=a.node;}else n=e;if(!n.node_ops.setattr)throw new o.ErrnoError(63);n.node_ops.setattr(n,{mode:r&4095|n.mode&-4096,timestamp:Date.now()});},lchmod(e,r){o.chmod(e,r,!0);},fchmod(e,r){var t=o.getStreamChecked(e);o.chmod(t.node,r);},chown(e,r,t,n){var a;if(typeof e=="string"){var i=o.lookupPath(e,{follow:!n});a=i.node;}else a=e;if(!a.node_ops.setattr)throw new o.ErrnoError(63);a.node_ops.setattr(a,{timestamp:Date.now()});},lchown(e,r,t){o.chown(e,r,t,!0);},fchown(e,r,t){var n=o.getStreamChecked(e);o.chown(n.node,r,t);},truncate(e,r){if(r<0)throw new o.ErrnoError(28);var t;if(typeof e=="string"){var n=o.lookupPath(e,{follow:!0});t=n.node;}else t=e;if(!t.node_ops.setattr)throw new o.ErrnoError(63);if(o.isDir(t.mode))throw new o.ErrnoError(31);if(!o.isFile(t.mode))throw new o.ErrnoError(28);var a=o.nodePermissions(t,"w");if(a)throw new o.ErrnoError(a);t.node_ops.setattr(t,{size:r,timestamp:Date.now()});},ftruncate(e,r){var t=o.getStreamChecked(e);if(!(t.flags&2097155))throw new o.ErrnoError(28);o.truncate(t.node,r);},utime(e,r,t){var n=o.lookupPath(e,{follow:!0}),a=n.node;a.node_ops.setattr(a,{timestamp:Math.max(r,t)});},open(e,r,t){if(e==="")throw new o.ErrnoError(44);r=typeof r=="string"?ut(r):r,r&64?(t=typeof t>"u"?438:t,t=t&4095|32768):t=0;var n;if(typeof e=="object")n=e;else {e=C.normalize(e);try{var a=o.lookupPath(e,{follow:!(r&131072)});n=a.node;}catch{}}var i=!1;if(r&64)if(n){if(r&128)throw new o.ErrnoError(20)}else n=o.mknod(e,t,0),i=!0;if(!n)throw new o.ErrnoError(44);if(o.isChrdev(n.mode)&&(r&=-513),r&65536&&!o.isDir(n.mode))throw new o.ErrnoError(54);if(!i){var u=o.mayOpen(n,r);if(u)throw new o.ErrnoError(u)}r&512&&!i&&o.truncate(n,0),r&=-131713;var l=o.createStream({node:n,path:o.getPath(n),flags:r,seekable:!0,position:0,stream_ops:n.stream_ops,ungotten:[],error:!1});return l.stream_ops.open&&l.stream_ops.open(l),s.logReadFiles&&!(r&1)&&(o.readFiles||(o.readFiles={}),e in o.readFiles||(o.readFiles[e]=1)),l},close(e){if(o.isClosed(e))throw new o.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e);}catch(r){throw r}finally{o.closeStream(e.fd);}e.fd=null;},isClosed(e){return e.fd===null},llseek(e,r,t){if(o.isClosed(e))throw new o.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new o.ErrnoError(70);if(t!=0&&t!=1&&t!=2)throw new o.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read(e,r,t,n,a){if(n<0||a<0)throw new o.ErrnoError(28);if(o.isClosed(e))throw new o.ErrnoError(8);if((e.flags&2097155)===1)throw new o.ErrnoError(8);if(o.isDir(e.node.mode))throw new o.ErrnoError(31);if(!e.stream_ops.read)throw new o.ErrnoError(28);var i=typeof a<"u";if(!i)a=e.position;else if(!e.seekable)throw new o.ErrnoError(70);var u=e.stream_ops.read(e,r,t,n,a);return i||(e.position+=u),u},write(e,r,t,n,a,i){if(n<0||a<0)throw new o.ErrnoError(28);if(o.isClosed(e))throw new o.ErrnoError(8);if(!(e.flags&2097155))throw new o.ErrnoError(8);if(o.isDir(e.node.mode))throw new o.ErrnoError(31);if(!e.stream_ops.write)throw new o.ErrnoError(28);e.seekable&&e.flags&1024&&o.llseek(e,0,2);var u=typeof a<"u";if(!u)a=e.position;else if(!e.seekable)throw new o.ErrnoError(70);var l=e.stream_ops.write(e,r,t,n,a,i);return u||(e.position+=l),l},allocate(e,r,t){if(o.isClosed(e))throw new o.ErrnoError(8);if(r<0||t<=0)throw new o.ErrnoError(28);if(!(e.flags&2097155))throw new o.ErrnoError(8);if(!o.isFile(e.node.mode)&&!o.isDir(e.node.mode))throw new o.ErrnoError(43);if(!e.stream_ops.allocate)throw new o.ErrnoError(138);e.stream_ops.allocate(e,r,t);},mmap(e,r,t,n,a){if(n&2&&!(a&2)&&(e.flags&2097155)!==2)throw new o.ErrnoError(2);if((e.flags&2097155)===1)throw new o.ErrnoError(2);if(!e.stream_ops.mmap)throw new o.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,a)},msync(e,r,t,n,a){return e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,a):0},ioctl(e,r,t){if(!e.stream_ops.ioctl)throw new o.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile(e,r={}){if(r.flags=r.flags||0,r.encoding=r.encoding||"binary",r.encoding!=="utf8"&&r.encoding!=="binary")throw new Error(`Invalid encoding type "${r.encoding}"`);var t,n=o.open(e,r.flags),a=o.stat(e),i=a.size,u=new Uint8Array(i);return o.read(n,u,0,i,0),r.encoding==="utf8"?t=Fe(u,0):r.encoding==="binary"&&(t=u),o.close(n),t},writeFile(e,r,t={}){t.flags=t.flags||577;var n=o.open(e,t.flags,t.mode);if(typeof r=="string"){var a=new Uint8Array(rr(r)+1),i=tr(r,a,0,a.length);o.write(n,a,0,i,void 0,t.canOwn);}else if(ArrayBuffer.isView(r))o.write(n,r,0,r.byteLength,void 0,t.canOwn);else throw new Error("Unsupported data type");o.close(n);},cwd:()=>o.currentPath,chdir(e){var r=o.lookupPath(e,{follow:!0});if(r.node===null)throw new o.ErrnoError(44);if(!o.isDir(r.node.mode))throw new o.ErrnoError(54);var t=o.nodePermissions(r.node,"x");if(t)throw new o.ErrnoError(t);o.currentPath=r.path;},createDefaultDirectories(){o.mkdir("/tmp"),o.mkdir("/home"),o.mkdir("/home/web_user");},createDefaultDevices(){o.mkdir("/dev"),o.registerDevice(o.makedev(1,3),{read:()=>0,write:(n,a,i,u,l)=>u}),o.mkdev("/dev/null",o.makedev(1,3)),Me.register(o.makedev(5,0),Me.default_tty_ops),Me.register(o.makedev(6,0),Me.default_tty1_ops),o.mkdev("/dev/tty",o.makedev(5,0)),o.mkdev("/dev/tty1",o.makedev(6,0));var e=new Uint8Array(1024),r=0,t=()=>(r===0&&(r=_r(e).byteLength),e[--r]);o.createDevice("/dev","random",t),o.createDevice("/dev","urandom",t),o.mkdir("/dev/shm"),o.mkdir("/dev/shm/tmp");},createSpecialDirectories(){o.mkdir("/proc");var e=o.mkdir("/proc/self");o.mkdir("/proc/self/fd"),o.mount({mount(){var r=o.createNode(e,"fd",16895,73);return r.node_ops={lookup(t,n){var a=+n,i=o.getStreamChecked(a),u={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>i.path}};return u.parent=u,u}},r}},{},"/proc/self/fd");},createStandardStreams(){s.stdin?o.createDevice("/dev","stdin",s.stdin):o.symlink("/dev/tty","/dev/stdin"),s.stdout?o.createDevice("/dev","stdout",null,s.stdout):o.symlink("/dev/tty","/dev/stdout"),s.stderr?o.createDevice("/dev","stderr",null,s.stderr):o.symlink("/dev/tty1","/dev/stderr");o.open("/dev/stdin",0);o.open("/dev/stdout",1);o.open("/dev/stderr",1);},staticInit(){[44].forEach(e=>{o.genericErrors[e]=new o.ErrnoError(e),o.genericErrors[e].stack="";}),o.nameTable=new Array(4096),o.mount(D,{},"/"),o.createDefaultDirectories(),o.createDefaultDevices(),o.createSpecialDirectories(),o.filesystems={MEMFS:D};},init(e,r,t){o.init.initialized=!0,s.stdin=e||s.stdin,s.stdout=r||s.stdout,s.stderr=t||s.stderr,o.createStandardStreams();},quit(){o.init.initialized=!1;for(var e=0;ethis.length-1||y<0)){var O=y%this.chunkSize,te=y/this.chunkSize|0;return this.getter(te)[O]}}setDataGetter(y){this.getter=y;}cacheLength(){var y=new XMLHttpRequest;if(y.open("HEAD",t,!1),y.send(null),!(y.status>=200&&y.status<300||y.status===304))throw new Error("Couldn't load "+t+". Status: "+y.status);var O=Number(y.getResponseHeader("Content-length")),te,de=(te=y.getResponseHeader("Accept-Ranges"))&&te==="bytes",ne=(te=y.getResponseHeader("Content-Encoding"))&&te==="gzip",fe=1024*1024;de||(fe=O);var ve=(ge,xe)=>{if(ge>xe)throw new Error("invalid range ("+ge+", "+xe+") or no bytes requested!");if(xe>O-1)throw new Error("only "+O+" bytes available! programmer error!");var ee=new XMLHttpRequest;if(ee.open("GET",t,!1),O!==fe&&ee.setRequestHeader("Range","bytes="+ge+"-"+xe),ee.responseType="arraybuffer",ee.overrideMimeType&&ee.overrideMimeType("text/plain; charset=x-user-defined"),ee.send(null),!(ee.status>=200&&ee.status<300||ee.status===304))throw new Error("Couldn't load "+t+". Status: "+ee.status);return ee.response!==void 0?new Uint8Array(ee.response||[]):or(ee.responseText||"",!0)},Ie=this;Ie.setDataGetter(ge=>{var xe=ge*fe,ee=(ge+1)*fe-1;if(ee=Math.min(ee,O-1),typeof Ie.chunks[ge]>"u"&&(Ie.chunks[ge]=ve(xe,ee)),typeof Ie.chunks[ge]>"u")throw new Error("doXHR failed!");return Ie.chunks[ge]}),(ne||!O)&&(fe=O=1,O=this.getter(0).length,fe=O,X("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=O,this._chunkSize=fe,this.lengthKnown=!0;}get length(){return this.lengthKnown||this.cacheLength(),this._length}get chunkSize(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}if(typeof XMLHttpRequest<"u"){throw "Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var u=new i,l={isDevice:!1,contents:u};}else var l={isDevice:!1,url:t};var v=o.createFile(e,r,l,n,a);l.contents?v.contents=l.contents:l.url&&(v.contents=null,v.url=l.url),Object.defineProperties(v,{usedBytes:{get:function(){return this.contents.length}}});var E={},G=Object.keys(v.stream_ops);G.forEach(B=>{var y=v.stream_ops[B];E[B]=(...O)=>(o.forceLoadFile(v),y(...O));});function x(B,y,O,te,de){var ne=B.node.contents;if(de>=ne.length)return 0;var fe=Math.min(ne.length-de,te);if(ne.slice)for(var ve=0;ve(o.forceLoadFile(v),x(B,y,O,te,de)),E.mmap=(B,y,O,te,de)=>{o.forceLoadFile(v);var ne=wr(y);if(!ne)throw new o.ErrnoError(48);return x(B,w,ne,y,O),{ptr:ne,allocated:!0}},v.stream_ops=E,v}},ue=(e,r)=>e?Fe(j,e,r):"",U={DEFAULT_POLLMASK:5,calculateAt(e,r,t){if(C.isAbs(r))return r;var n;if(e===-100)n=o.cwd();else {var a=U.getStreamFromFD(e);n=a.path;}if(r.length==0){if(!t)throw new o.ErrnoError(44);return n}return C.join2(n,r)},doStat(e,r,t){var n=e(r);b[t>>2]=n.dev,b[t+4>>2]=n.mode,k[t+8>>2]=n.nlink,b[t+12>>2]=n.uid,b[t+16>>2]=n.gid,b[t+20>>2]=n.rdev,$=[n.size>>>0,(R=n.size,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+24>>2]=$[0],b[t+28>>2]=$[1],b[t+32>>2]=4096,b[t+36>>2]=n.blocks;var a=n.atime.getTime(),i=n.mtime.getTime(),u=n.ctime.getTime();return $=[Math.floor(a/1e3)>>>0,(R=Math.floor(a/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+40>>2]=$[0],b[t+44>>2]=$[1],k[t+48>>2]=a%1e3*1e3,$=[Math.floor(i/1e3)>>>0,(R=Math.floor(i/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+56>>2]=$[0],b[t+60>>2]=$[1],k[t+64>>2]=i%1e3*1e3,$=[Math.floor(u/1e3)>>>0,(R=Math.floor(u/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+72>>2]=$[0],b[t+76>>2]=$[1],k[t+80>>2]=u%1e3*1e3,$=[n.ino>>>0,(R=n.ino,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+88>>2]=$[0],b[t+92>>2]=$[1],0},doMsync(e,r,t,n,a){if(!o.isFile(r.node.mode))throw new o.ErrnoError(43);if(n&2)return 0;var i=j.slice(e,e+t);o.msync(r,i,a,t,n);},getStreamFromFD(e){var r=o.getStreamChecked(e);return r},varargs:void 0,getStr(e){var r=ue(e);return r}};function pt(e,r,t,n){try{if(r=U.getStr(r),r=U.calculateAt(e,r),t&-8)return -28;var a=o.lookupPath(r,{follow:!0}),i=a.node;if(!i)return -44;var u="";return t&4&&(u+="r"),t&2&&(u+="w"),t&1&&(u+="x"),u&&o.nodePermissions(i,u)?-2:0}catch(l){if(typeof o>"u"||l.name!=="ErrnoError")throw l;return -l.errno}}function Se(){var e=b[+U.varargs>>2];return U.varargs+=4,e}var Oe=Se;function lt(e,r,t){U.varargs=t;try{var n=U.getStreamFromFD(e);switch(r){case 0:{var a=Se();if(a<0)return -28;for(;o.streams[a];)a++;var i;return i=o.dupStream(n,a),i.fd}case 1:case 2:return 0;case 3:return n.flags;case 4:{var a=Se();return n.flags|=a,0}case 12:{var a=Oe(),u=0;return W[a+u>>1]=2,0}case 13:case 14:return 0}return -28}catch(l){if(typeof o>"u"||l.name!=="ErrnoError")throw l;return -l.errno}}function ct(e,r){try{var t=U.getStreamFromFD(e);return U.doStat(o.stat,t.path,r)}catch(n){if(typeof o>"u"||n.name!=="ErrnoError")throw n;return -n.errno}}function dt(e,r,t){U.varargs=t;try{var n=U.getStreamFromFD(e);switch(r){case 21509:return n.tty?0:-59;case 21505:{if(!n.tty)return -59;if(n.tty.ops.ioctl_tcgets){var a=n.tty.ops.ioctl_tcgets(n),i=Oe();b[i>>2]=a.c_iflag||0,b[i+4>>2]=a.c_oflag||0,b[i+8>>2]=a.c_cflag||0,b[i+12>>2]=a.c_lflag||0;for(var u=0;u<32;u++)w[i+u+17]=a.c_cc[u]||0;return 0}return 0}case 21510:case 21511:case 21512:return n.tty?0:-59;case 21506:case 21507:case 21508:{if(!n.tty)return -59;if(n.tty.ops.ioctl_tcsets){for(var i=Oe(),l=b[i>>2],v=b[i+4>>2],E=b[i+8>>2],G=b[i+12>>2],x=[],u=0;u<32;u++)x.push(w[i+u+17]);return n.tty.ops.ioctl_tcsets(n.tty,r,{c_iflag:l,c_oflag:v,c_cflag:E,c_lflag:G,c_cc:x})}return 0}case 21519:{if(!n.tty)return -59;var i=Oe();return b[i>>2]=0,0}case 21520:return n.tty?-28:-59;case 21531:{var i=Oe();return o.ioctl(n,r,i)}case 21523:{if(!n.tty)return -59;if(n.tty.ops.ioctl_tiocgwinsz){var B=n.tty.ops.ioctl_tiocgwinsz(n.tty),i=Oe();W[i>>1]=B[0],W[i+2>>1]=B[1];}return 0}case 21524:return n.tty?0:-59;case 21515:return n.tty?0:-59;default:return -28}}catch(y){if(typeof o>"u"||y.name!=="ErrnoError")throw y;return -y.errno}}function ft(e,r,t,n){try{r=U.getStr(r);var a=n&256,i=n&4096;return n=n&-6401,r=U.calculateAt(e,r,i),U.doStat(a?o.lstat:o.stat,r,t)}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return -u.errno}}function vt(e,r,t,n){U.varargs=n;try{r=U.getStr(r),r=U.calculateAt(e,r);var a=n?Se():0;return o.open(r,t,a).fd}catch(i){if(typeof o>"u"||i.name!=="ErrnoError")throw i;return -i.errno}}var Ke=(e,r,t)=>tr(e,j,r,t);function mt(e,r,t,n){try{if(r=U.getStr(r),r=U.calculateAt(e,r),n<=0)return -28;var a=o.readlink(r),i=Math.min(n,rr(a)),u=w[t+i];return Ke(a,t,n+1),w[t+i]=u,i}catch(l){if(typeof o>"u"||l.name!=="ErrnoError")throw l;return -l.errno}}function _t(e){try{return e=U.getStr(e),o.rmdir(e),0}catch(r){if(typeof o>"u"||r.name!=="ErrnoError")throw r;return -r.errno}}function gt(e,r){try{return e=U.getStr(e),U.doStat(o.stat,e,r)}catch(t){if(typeof o>"u"||t.name!=="ErrnoError")throw t;return -t.errno}}function ht(e,r,t){try{return r=U.getStr(r),r=U.calculateAt(e,r),t===0?o.unlink(r):t===512?o.rmdir(r):_e("Invalid flags passed to unlinkat"),0}catch(n){if(typeof o>"u"||n.name!=="ErrnoError")throw n;return -n.errno}}var wt=()=>{_e("");},yt=1,bt=()=>yt,Et=(e,r,t)=>j.copyWithin(e,r,r+t),ar=(e,r)=>r+2097152>>>0<4194305-!!e?(e>>>0)+r*4294967296:NaN;function Mt(e,r,t,n,a,i,u,l){var v=ar(a,i);try{if(isNaN(v))return 61;var E=U.getStreamFromFD(n),G=o.mmap(E,e,v,r,t),x=G.ptr;return b[u>>2]=G.allocated,k[l>>2]=x,0}catch(B){if(typeof o>"u"||B.name!=="ErrnoError")throw B;return -B.errno}}function Dt(e,r,t,n,a,i,u){var l=ar(i,u);try{var v=U.getStreamFromFD(a);t&2&&U.doMsync(e,v,r,n,l);}catch(E){if(typeof o>"u"||E.name!=="ErrnoError")throw E;return -E.errno}}var Xt=(e,r,t,n)=>{var a=new Date().getFullYear(),i=new Date(a,0,1),u=new Date(a,6,1),l=i.getTimezoneOffset(),v=u.getTimezoneOffset(),E=Math.max(l,v);k[e>>2]=E*60,b[r>>2]=+(l!=v);var G=y=>{var O=y>=0?"-":"+",te=Math.abs(y),de=String(Math.floor(te/60)).padStart(2,"0"),ne=String(te%60).padStart(2,"0");return `UTC${O}${de}${ne}`},x=G(l),B=G(v);v{ir.length=0;for(var t;t=j[e++];){var n=t!=105;n&=t!=112,r+=n&&r%8?4:0,ir.push(t==112?k[r>>2]:t==105?b[r>>2]:oe[r>>3]),r+=n?8:4;}return ir},Bt=(e,r,t)=>{var n=Lt(r,t);return Qr[e](...n)},Rt=(e,r,t)=>Bt(e,r,t),kt=()=>Date.now();var Ft=()=>2147483648,Ot=e=>{var r=S.buffer,t=(e-r.byteLength+65535)/65536;try{return S.grow(t),K(),1}catch{}},xt=e=>{var r=j.length;e>>>=0;var t=Ft();if(e>t)return !1;for(var n=(v,E)=>v+(E-v%E)%E,a=1;a<=4;a*=2){var i=r*(1+.2/a);i=Math.min(i,e+100663296);var u=Math.min(t,n(Math.max(e,i),65536)),l=Ot(u);if(l)return !0}return !1},sr={},Ut=()=>I,Ce=()=>{if(!Ce.strings){var e=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",r={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:e,_:Ut()};for(var t in sr)sr[t]===void 0?delete r[t]:r[t]=sr[t];var n=[];for(var t in r)n.push(`${t}=${r[t]}`);Ce.strings=n;}return Ce.strings},Nt=(e,r)=>{for(var t=0;t{var t=0;return Ce().forEach((n,a)=>{var i=r+t;k[e+a*4>>2]=i,Nt(n,i),t+=n.length+1;}),0},Kt=(e,r)=>{var t=Ce();k[e>>2]=t.length;var n=0;return t.forEach(a=>n+=a.length+1),k[r>>2]=n,0},Zt=e=>{(P=!0),Q(e,new qr(e));},Yt=(e,r)=>{Zt(e);},zt=Yt;function jt(e){try{var r=U.getStreamFromFD(e);return o.close(r),0}catch(t){if(typeof o>"u"||t.name!=="ErrnoError")throw t;return t.errno}}var Pt=(e,r,t,n)=>{for(var a=0,i=0;i>2],l=k[r+4>>2];r+=8;var v=o.read(e,w,u,l,n);if(v<0)return -1;if(a+=v,v>2]=i,0}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return u.errno}}function St(e,r,t,n,a){var i=ar(r,t);try{if(isNaN(i))return 61;var u=U.getStreamFromFD(e);return o.llseek(u,i,n),$=[u.position>>>0,(R=u.position,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[a>>2]=$[0],b[a+4>>2]=$[1],u.getdents&&i===0&&n===0&&(u.getdents=null),0}catch(l){if(typeof o>"u"||l.name!=="ErrnoError")throw l;return l.errno}}var Ht=(e,r,t,n)=>{for(var a=0,i=0;i>2],l=k[r+4>>2];r+=8;var v=o.write(e,w,u,l,n);if(v<0)return -1;a+=v;}return a};function Tt(e,r,t,n){try{var a=U.getStreamFromFD(e),i=Ht(a,r,t);return k[n>>2]=i,0}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return u.errno}}o.createPreloadedFile=st,o.staticInit();var Qt={a:Ar,k:pt,h:lt,y:ct,z:dt,w:ft,d:vt,r:mt,s:_t,x:gt,t:ht,l:wt,i:bt,j:Et,m:Mt,n:Dt,p:Xt,A:Rt,b:kt,q:xt,u:Vt,v:Kt,e:zt,c:jt,f:Jt,o:St,g:Tt},N=Tr();s._webidl_free=e=>(s._webidl_free=N.D)(e);s._free=e=>(s._free=N.E)(e);s._webidl_malloc=e=>(s._webidl_malloc=N.F)(e);s._malloc=e=>(s._malloc=N.G)(e);var yr=s._emscripten_bind_VoidPtr___destroy___0=e=>(yr=s._emscripten_bind_VoidPtr___destroy___0=N.H)(e),br=s._emscripten_bind_Graphviz_Graphviz_2=(e,r)=>(br=s._emscripten_bind_Graphviz_Graphviz_2=N.I)(e,r),Er=s._emscripten_bind_Graphviz_version_0=()=>(Er=s._emscripten_bind_Graphviz_version_0=N.J)(),Mr=s._emscripten_bind_Graphviz_lastError_0=()=>(Mr=s._emscripten_bind_Graphviz_lastError_0=N.K)(),Dr=s._emscripten_bind_Graphviz_createFile_2=(e,r,t)=>(Dr=s._emscripten_bind_Graphviz_createFile_2=N.L)(e,r,t),Xr=s._emscripten_bind_Graphviz_layout_3=(e,r,t,n)=>(Xr=s._emscripten_bind_Graphviz_layout_3=N.M)(e,r,t,n),Lr=s._emscripten_bind_Graphviz_acyclic_3=(e,r,t,n)=>(Lr=s._emscripten_bind_Graphviz_acyclic_3=N.N)(e,r,t,n),Br=s._emscripten_bind_Graphviz_tred_3=(e,r,t,n)=>(Br=s._emscripten_bind_Graphviz_tred_3=N.O)(e,r,t,n),Rr=s._emscripten_bind_Graphviz_unflatten_4=(e,r,t,n,a)=>(Rr=s._emscripten_bind_Graphviz_unflatten_4=N.P)(e,r,t,n,a),kr=s._emscripten_bind_Graphviz_get_layout_result_0=e=>(kr=s._emscripten_bind_Graphviz_get_layout_result_0=N.Q)(e),Gr=s._emscripten_bind_Graphviz_set_layout_result_1=(e,r)=>(Gr=s._emscripten_bind_Graphviz_set_layout_result_1=N.R)(e,r),Fr=s._emscripten_bind_Graphviz_get_acyclic_outFile_0=e=>(Fr=s._emscripten_bind_Graphviz_get_acyclic_outFile_0=N.S)(e),Or=s._emscripten_bind_Graphviz_set_acyclic_outFile_1=(e,r)=>(Or=s._emscripten_bind_Graphviz_set_acyclic_outFile_1=N.T)(e,r),xr=s._emscripten_bind_Graphviz_get_acyclic_num_rev_0=e=>(xr=s._emscripten_bind_Graphviz_get_acyclic_num_rev_0=N.U)(e),Ur=s._emscripten_bind_Graphviz_set_acyclic_num_rev_1=(e,r)=>(Ur=s._emscripten_bind_Graphviz_set_acyclic_num_rev_1=N.V)(e,r),Nr=s._emscripten_bind_Graphviz_get_tred_out_0=e=>(Nr=s._emscripten_bind_Graphviz_get_tred_out_0=N.W)(e),Vr=s._emscripten_bind_Graphviz_set_tred_out_1=(e,r)=>(Vr=s._emscripten_bind_Graphviz_set_tred_out_1=N.X)(e,r),Kr=s._emscripten_bind_Graphviz_get_tred_err_0=e=>(Kr=s._emscripten_bind_Graphviz_get_tred_err_0=N.Y)(e),Cr=s._emscripten_bind_Graphviz_set_tred_err_1=(e,r)=>(Cr=s._emscripten_bind_Graphviz_set_tred_err_1=N.Z)(e,r),Ir=s._emscripten_bind_Graphviz___destroy___0=e=>(Ir=s._emscripten_bind_Graphviz___destroy___0=N._)(e),Zr=(e,r)=>(Zr=N.aa)(e,r),Yr=e=>(Yr=N.ba)(e);s.UTF8ToString=ue;var He;be=function e(){He||zr(),He||(be=e);};function zr(){if(ye>0||(Re(),ye>0))return;function e(){He||(He=!0,s.calledRun=!0,!P&&(Ne(),f(s),Pe()));}e();}zr();function pe(){}pe.prototype=Object.create(pe.prototype),pe.prototype.constructor=pe,pe.prototype.__class__=pe,pe.__cache__={},s.WrapperObject=pe;function Te(e){return (e||pe).__cache__}s.getCache=Te;function ur(e,r){var t=Te(r),n=t[e];return n||(n=Object.create((r||pe).prototype),n.ptr=e,t[e]=n)}s.wrapPointer=ur;function ro(e,r){return ur(e.ptr,r)}s.castObject=ro,s.NULL=ur(0);function to(e){if(!e.__destroy__)throw "Error: Cannot destroy object. (Did you create it yourself?)";e.__destroy__(),delete Te(e.__class__)[e.ptr];}s.destroy=to;function oo(e,r){return e.ptr===r.ptr}s.compare=oo;function no(e){return e.ptr}s.getPointer=no;function ao(e){return e.__class__}s.getClass=ao;var F={buffer:0,size:0,pos:0,temps:[],needed:0,prepare(){if(F.needed){for(var e=0;e=F.size?(J(n>0),F.needed+=n,a=s._webidl_malloc(n),F.temps.push(a)):(a=F.buffer+F.pos,F.pos+=n),a},copy(e,r,t){t/=r.BYTES_PER_ELEMENT;for(var n=0;n?@[]^_`{|}~"';function Lo(p){let c=p.length,d=[],s=0,f=0,m=-1;for(let _=0;_88?13:14;do d.push(s&255),s>>=8,f-=8;while(f>7);m=-1;}}return m>-1&&d.push((s|m< - -`}}function Fo(p){return p.map(Go)}function Oo(p,c){let d={images:[],files:[],...c};[...d.files,...Fo(d.images)].forEach(s=>p.createFile(s.path,s.data));}var xo=class Ue{constructor(c){this._module=c;}static load(){return Ro().then(c=>new Ue(c))}static unload(){ko();}version(){return this._module.Graphviz.prototype.version()}layout(c,d="svg",s="dot",f){if(!c)return "";let m=new this._module.Graphviz(f?.yInvert?1:0,f?.nop?f?.nop:0),_="",h="";try{Oo(m,f);try{_=m.layout(c,d,s);}catch(M){h=M.message;}h=m.lastError()||h;}finally{this._module.destroy(m);}if(!_&&h)throw Ue.unload(),new Error(h);return _}acyclic(c,d=!1,s=!1){if(!c)return {acyclic:!1,num_rev:0,outFile:""};let f=new this._module.Graphviz,m=!1,_=0,h="",M="";try{try{m=f.acyclic(c,d,s),_=f.acyclic_num_rev,h=f.acyclic_outFile;}catch(I){M=I.message;}M=f.lastError()||M;}finally{this._module.destroy(f);}if(M)throw Ue.unload(),new Error(M);return {acyclic:m,num_rev:_,outFile:h}}tred(c,d=!1,s=!1){if(!c)return {out:"",err:""};let f=new this._module.Graphviz,m="",_="",h="";try{try{f.tred(c,d,s),m=f.tred_out,_=f.tred_err;}catch(M){h=M.message;}h=f.lastError()||h;}finally{this._module.destroy(f);}if(!m&&h)throw Ue.unload(),new Error(h);return {out:m,err:_}}unflatten(c,d=0,s=!1,f=0){if(!c)return "";let m=new this._module.Graphviz,_="",h="";try{try{_=m.unflatten(c,d,s,f);}catch(M){h=M.message;}h=m.lastError()||h;}finally{this._module.destroy(m);}if(!_&&h)throw Ue.unload(),new Error(h);return _}circo(c,d="svg",s){return this.layout(c,d,"circo",s)}dot(c,d="svg",s){return this.layout(c,d,"dot",s)}fdp(c,d="svg",s){return this.layout(c,d,"fdp",s)}sfdp(c,d="svg",s){return this.layout(c,d,"sfdp",s)}neato(c,d="svg",s){return this.layout(c,d,"neato",s)}osage(c,d="svg",s){return this.layout(c,d,"osage",s)}patchwork(c,d="svg",s){return this.layout(c,d,"patchwork",s)}twopi(c,d="svg",s){return this.layout(c,d,"twopi",s)}nop(c){return this.layout(c,"dot","nop")}nop2(c){return this.layout(c,"dot","nop2")}}; - - function shallowCopyObject(obj) { - return Object.assign({}, obj); - } - function roundTo2Decimals(x) { - return Math.round(x * 100.0) / 100.0; - } - - function convertToPathData(originalData, guideData) { - if (originalData.tag == 'polygon') { - var newData = shallowCopyObject(originalData); - newData.tag = 'path'; - var originalAttributes = originalData.attributes; - var newAttributes = shallowCopyObject(originalAttributes); - var newPointsString = originalAttributes.points; - if (guideData.tag == 'polygon') { - var bbox = originalData.bbox; - bbox.cx = bbox.x + bbox.width / 2; - bbox.cy = bbox.y + bbox.height / 2; - var pointsString = originalAttributes.points; - var pointStrings = pointsString.split(' '); - var normPoints = pointStrings.map(function (p) { - var xy = p.split(','); - return [xy[0] - bbox.cx, xy[1] - bbox.cy]; - }); - var x0 = normPoints[normPoints.length - 1][0]; - var y0 = normPoints[normPoints.length - 1][1]; - for (var i = 0; i < normPoints.length; i++, x0 = x1, y0 = y1) { - var x1 = normPoints[i][0]; - var y1 = normPoints[i][1]; - var dx = x1 - x0; - var dy = y1 - y0; - if (dy == 0) { - continue; - } else { - var x2 = x0 - y0 * dx / dy; - } - if (0 <= x2 && x2 < Infinity && (x0 <= x2 && x2 <= x1 || x1 <= x2 && x2 <= x0)) { - break; - } - } - var newPointStrings = [[bbox.cx + x2, bbox.cy + 0].join(',')]; - newPointStrings = newPointStrings.concat(pointStrings.slice(i)); - newPointStrings = newPointStrings.concat(pointStrings.slice(0, i)); - newPointsString = newPointStrings.join(' '); - } - newAttributes['d'] = 'M' + newPointsString + 'z'; - delete newAttributes.points; - newData.attributes = newAttributes; - } else /* if (originalData.tag == 'ellipse') */{ - var newData = shallowCopyObject(originalData); - newData.tag = 'path'; - var originalAttributes = originalData.attributes; - var newAttributes = shallowCopyObject(originalAttributes); - var cx = originalAttributes.cx; - var cy = originalAttributes.cy; - var rx = originalAttributes.rx; - var ry = originalAttributes.ry; - if (guideData.tag == 'polygon') { - var bbox = guideData.bbox; - bbox.cx = bbox.x + bbox.width / 2; - bbox.cy = bbox.y + bbox.height / 2; - var p = guideData.attributes.points.split(' ')[0].split(','); - var sx = p[0]; - var sy = p[1]; - var dx = sx - bbox.cx; - var dy = sy - bbox.cy; - var l = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - var cosA = dx / l; - var sinA = -dy / l; - } else { - // if (guideData.tag == 'path') { - // FIXME: add support for getting start position from path - var cosA = 1; - var sinA = 0; - } - var x1 = rx * cosA; - var y1 = -ry * sinA; - var x2 = rx * -cosA; - var y2 = -ry * -sinA; - var dx = x2 - x1; - var dy = y2 - y1; - newAttributes['d'] = 'M ' + cx + ' ' + cy + ' m ' + x1 + ',' + y1 + ' a ' + rx + ',' + ry + ' 0 1,0 ' + dx + ',' + dy + ' a ' + rx + ',' + ry + ' 0 1,0 ' + -dx + ',' + -dy + 'z'; - delete newAttributes.cx; - delete newAttributes.cy; - delete newAttributes.rx; - delete newAttributes.ry; - newData.attributes = newAttributes; - } - return newData; - } - function translatePointsAttribute(pointsString, x, y) { - var pointStrings = pointsString.split(' '); - var points = pointStrings.map(function (p) { - return p.split(','); - }); - var points = pointStrings.map(function (p) { - return [roundTo2Decimals(+x + +p.split(',')[0]), roundTo2Decimals(+y + +p.split(',')[1])]; - }); - var pointStrings = points.map(function (p) { - return p.join(','); - }); - var pointsString = pointStrings.join(' '); - return pointsString; - } - function translateDAttribute(d, x, y) { - var pointStrings = d.split(/[A-Z ]/); - pointStrings.shift(); - var commands = d.split(/[^[A-Z ]+/); - var points = pointStrings.map(function (p) { - return p.split(','); - }); - var points = pointStrings.map(function (p) { - return [roundTo2Decimals(+x + +p.split(',')[0]), roundTo2Decimals(+y + +p.split(',')[1])]; - }); - var pointStrings = points.map(function (p) { - return p.join(','); - }); - d = commands.reduce(function (arr, v, i) { - return arr.concat(v, pointStrings[i]); - }, []).join(''); - return d; - } - - function initViz() { - var _this = this; - // force JIT compilation of @hpcc-js/wasm - try { - xo.load().then(function (graphviz) { - graphviz.layout("", "svg", "dot"); - _this.layoutSync = graphviz.layout.bind(graphviz); - if (_this._worker == null) { - _this._dispatch.call("initEnd", _this); - } - if (_this._afterInit) { - _this._afterInit(); - } - }); - // after the port to ESM modules, we don't know how to trigger this so - // we just disable it from coverage - /* c8 ignore start */ - } catch (error) { - // we end up here when the the script tag type used to load - // the "@hpcc-js/wasm" script is not "application/javascript" - // or "text/javascript", but typically "javascript/worker". In - // this case the browser does not load the script since it's - // unnecessary because it's loaded by the web worker - // instead. This is expected so we just ignore the error. - } - /* c8 ignore stop */ - if (this._worker != null) { - var vizURL = this._vizURL; - var graphvizInstance = this; - this._workerPort.onmessage = function (event) { - var callback = graphvizInstance._workerCallbacks.shift(); - callback.call(graphvizInstance, event); - }; - if (!vizURL.match(/^https?:\/\/|^\/\//i)) { - // Local URL. Prepend with local domain to be usable in web worker - vizURL = new window.URL(vizURL, document.location.href).href; - } - postMessage.call(this, { - type: "layout", - dot: "", - engine: 'dot', - vizURL: vizURL - }, function (event) { - switch (event.data.type) { - } - }); - postMessage.call(this, { - type: "version" - }, function (event) { - switch (event.data.type) { - case "version": - graphvizInstance._graphvizVersion = event.data.version; - graphvizInstance._dispatch.call("initEnd", this); - break; - } - }); - } - } - function postMessage(message, callback) { - this._workerCallbacks.push(callback); - this._workerPort.postMessage(message); - } - function layout(src, engine, vizOptions, callback) { - if (this._worker) { - postMessage.call(this, { - type: "layout", - dot: src, - engine: engine, - options: vizOptions - }, function (event) { - callback.call(this, event.data); - }); - } else { - try { - var svgDoc = this.layoutSync(src, "svg", engine, vizOptions); - callback.call(this, { - type: 'done', - svg: svgDoc - }); - } catch (error) { - callback.call(this, { - type: 'error', - error: error.message - }); - } - } - } - function dot (src, callback) { - var graphvizInstance = this; - this._worker; - var engine = this._options.engine; - var images = this._images; - this._dispatch.call("start", this); - this._busy = true; - this._dispatch.call("layoutStart", this); - var vizOptions = { - images: images - }; - if (!this._worker && this.layoutSync == null) { - this._afterInit = this.dot.bind(this, src, callback); - return this; - } - this.layout(src, engine, vizOptions, function (data) { - switch (data.type) { - case "error": - if (graphvizInstance._onerror) { - graphvizInstance._onerror(data.error); - } else { - throw data.error.message; - } - break; - case "done": - var svgDoc = data.svg; - layoutDone.call(this, svgDoc, callback); - break; - } - }); - return this; - } - function layoutDone(svgDoc, callback) { - var keyMode = this._options.keyMode; - var tweenPaths = this._options.tweenPaths; - var tweenShapes = this._options.tweenShapes; - if (typeof this._options.tweenPrecision == 'string' && this._options.tweenPrecision.includes('%')) { - var tweenPrecision = +this._options.tweenPrecision.split('%')[0] / 100; - var tweenPrecisionIsRelative = this._options.tweenPrecision.includes('%'); - } else { - var tweenPrecision = this._options.tweenPrecision; - var tweenPrecisionIsRelative = false; - } - var growEnteringEdges = this._options.growEnteringEdges; - var dictionary = {}; - var prevDictionary = this._dictionary || {}; - var nodeDictionary = {}; - var prevNodeDictionary = this._nodeDictionary || {}; - function setKey(datum, index) { - var tag = datum.tag; - if (keyMode == 'index') { - datum.key = index; - } else if (tag[0] != '#') { - if (keyMode == 'id') { - datum.key = datum.attributes.id; - } else if (keyMode == 'title') { - var title = datum.children.find(function (childData) { - return childData.tag == 'title'; - }); - if (title) { - if (title.children.length > 0) { - datum.key = title.children[0].text; - } else { - datum.key = ''; - } - } - } - } - if (datum.key == null) { - if (tweenShapes) { - if (tag == 'ellipse' || tag == 'polygon') { - tag = 'path'; - } - } - datum.key = tag + '-' + index; - } - } - function setId(datum, parentData) { - var id = (parentData ? parentData.id + '.' : '') + datum.key; - datum.id = id; - } - function addToDictionary(datum) { - dictionary[datum.id] = datum; - } - function calculateAlternativeShapeData(datum, prevDatum) { - if (tweenShapes && datum.id in prevDictionary) { - if ((prevDatum.tag == 'polygon' || prevDatum.tag == 'ellipse' || prevDatum.tag == 'path') && (prevDatum.tag != datum.tag || datum.tag == 'polygon')) { - if (prevDatum.tag != 'path') { - datum.alternativeOld = convertToPathData(prevDatum, datum); - } - if (datum.tag != 'path') { - datum.alternativeNew = convertToPathData(datum, prevDatum); - } - } - } - } - function calculatePathTweenPoints(datum, prevDatum) { - if (tweenPaths && prevDatum && (prevDatum.tag == 'path' || datum.alternativeOld && datum.alternativeOld.tag == 'path')) { - var attribute_d = (datum.alternativeNew || datum).attributes.d; - if (datum.alternativeOld) { - var oldNode = createElementWithAttributes(datum.alternativeOld); - } else { - var oldNode = createElementWithAttributes(prevDatum); - } - (datum.alternativeOld || (datum.alternativeOld = {})).points = pathTweenPoints(oldNode, attribute_d, tweenPrecision, tweenPrecisionIsRelative); - } - } - function postProcessDataPass1Local(datum) { - var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; - var parentData = arguments.length > 2 ? arguments[2] : undefined; - setKey(datum, index); - setId(datum, parentData); - var id = datum.id; - var prevDatum = prevDictionary[id]; - addToDictionary(datum); - calculateAlternativeShapeData(datum, prevDatum); - calculatePathTweenPoints(datum, prevDatum); - var childTagIndexes = {}; - datum.children.forEach(function (childData) { - var childTag = childData.tag; - if (childTag == 'ellipse' || childTag == 'polygon') { - childTag = 'path'; - } - if (childTagIndexes[childTag] == null) { - childTagIndexes[childTag] = 0; - } - var childIndex = childTagIndexes[childTag]++; - postProcessDataPass1Local(childData, childIndex, datum); - }); - } - function addToNodeDictionary(datum) { - var tag = datum.tag; - if (growEnteringEdges && datum.parent) { - if (datum.parent.attributes["class"] == 'node') { - if (tag == 'title') { - if (datum.children.length > 0) { - var child = datum.children[0]; - var nodeId = child.text; - } else { - var nodeId = ''; - } - nodeDictionary[nodeId] = datum.parent; - } - } - } - } - function extractGrowingEdgesData(datum) { - var id = datum.id; - var tag = datum.tag; - var prevDatum = prevDictionary[id]; - if (growEnteringEdges && !prevDatum && datum.parent) { - if (isEdgeElement(datum)) { - if (tag == 'path' || tag == 'polygon') { - if (tag == 'polygon') { - var path = datum.parent.children.find(function (e) { - return e.tag == 'path'; - }); - if (path) { - datum.totalLength = path.totalLength; - } - } - var title = getEdgeTitle(datum); - var child = title.children[0]; - var nodeIds = child.text.split('->'); - if (nodeIds.length != 2) { - nodeIds = child.text.split('--'); - } - var startNodeId = nodeIds[0]; - var startNode = nodeDictionary[startNodeId]; - if (Object.hasOwn(prevNodeDictionary, startNodeId)) { - var prevStartNode = prevNodeDictionary[startNodeId]; - var i = startNode.children.findIndex(function (element, index) { - return element.tag == 'g'; - }); - if (i >= 0) { - var j = startNode.children[i].children.findIndex(function (element, index) { - return element.tag == 'a'; - }); - startNode = startNode.children[i].children[j]; - } - var i = prevStartNode.children.findIndex(function (element, index) { - return element.tag == 'g'; - }); - if (i >= 0) { - var j = prevStartNode.children[i].children.findIndex(function (element, index) { - return element.tag == 'a'; - }); - prevStartNode = prevStartNode.children[i].children[j]; - } - var startShapes = startNode.children; - for (var i = 0; i < startShapes.length; i++) { - if (startShapes[i].tag == 'polygon' || startShapes[i].tag == 'ellipse' || startShapes[i].tag == 'path' || startShapes[i].tag == 'text') { - var startShape = startShapes[i]; - break; - } - } - var prevStartShapes = prevStartNode.children; - for (var i = 0; i < prevStartShapes.length; i++) { - if (prevStartShapes[i].tag == 'polygon' || prevStartShapes[i].tag == 'ellipse' || prevStartShapes[i].tag == 'path' || prevStartShapes[i].tag == 'text') { - var prevStartShape = prevStartShapes[i]; - break; - } - } - if (prevStartShape && startShape) { - datum.offset = { - x: prevStartShape.center.x - startShape.center.x, - y: prevStartShape.center.y - startShape.center.y - }; - } else { - datum.offset = { - x: 0, - y: 0 - }; - } - } - } - } - } - } - function postProcessDataPass2Global(datum) { - addToNodeDictionary(datum); - extractGrowingEdgesData(datum); - datum.children.forEach(function (childData) { - postProcessDataPass2Global(childData); - }); - } - this._dispatch.call("layoutEnd", this); - var newDoc = d3__namespace.select(document.createDocumentFragment()).append('div'); - var parser = new window.DOMParser(); - var doc = parser.parseFromString(svgDoc, "image/svg+xml"); - newDoc.append(function () { - return doc.documentElement; - }); - var newSvg = newDoc.select('svg'); - var data = extractAllElementsData(newSvg); - this._dispatch.call('dataExtractEnd', this); - postProcessDataPass1Local(data); - this._dispatch.call('dataProcessPass1End', this); - postProcessDataPass2Global(data); - this._dispatch.call('dataProcessPass2End', this); - this._data = data; - this._dictionary = dictionary; - this._nodeDictionary = nodeDictionary; - this._extractData = function (element, childIndex, parentData) { - var data = extractAllElementsData(element); - postProcessDataPass1Local(data, childIndex, parentData); - postProcessDataPass2Global(data); - return data; - }; - this._busy = false; - this._dispatch.call('dataProcessEnd', this); - if (callback) { - callback.call(this); - } - if (this._queue.length > 0) { - var job = this._queue.shift(); - job.call(this); - } - } - - function renderDot (src, callback) { - var graphvizInstance = this; - this.dot(src, render); - function render() { - graphvizInstance.render(callback); - } - return this; - } - - function transition (name) { - if (name instanceof Function) { - this._transitionFactory = name; - } else { - this._transition = d3Transition.transition(name); - } - return this; - } - function active(name) { - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - if (svg.size() != 0) { - return d3Transition.active(svg.node(), name); - } else { - return null; - } - } - - function options (options) { - if (typeof options == 'undefined') { - return Object.assign({}, this._options); - } else { - for (var _i = 0, _Object$keys = Object.keys(options); _i < _Object$keys.length; _i++) { - var option = _Object$keys[_i]; - this._options[option] = options[option]; - } - return this; - } - } - - function width (width) { - this._options.width = width; - return this; - } - - function height (height) { - this._options.height = height; - return this; - } - - function scale (scale) { - this._options.scale = scale; - return this; - } - - function fit (fit) { - this._options.fit = fit; - return this; - } - - function attributer (callback) { - this._attributer = callback; - return this; - } - - function engine (engine) { - this._options.engine = engine; - return this; - } - - function images (path, width, height) { - this._images.push({ - path: path, - width: width, - height: height - }); - return this; - } - - function keyMode (keyMode) { - if (!this._keyModes.has(keyMode)) { - throw Error('Illegal keyMode: ' + keyMode); - } - if (keyMode != this._options.keyMode && this._data != null) { - throw Error('Too late to change keyMode'); - } - this._options.keyMode = keyMode; - return this; - } - - function fade (enable) { - this._options.fade = enable; - return this; - } - - function tweenPaths (enable) { - this._options.tweenPaths = enable; - return this; - } - - function tweenShapes (enable) { - this._options.tweenShapes = enable; - if (enable) { - this._options.tweenPaths = true; - } - return this; - } - - function convertEqualSidedPolygons (enable) { - this._options.convertEqualSidedPolygons = enable; - return this; - } - - function tweenPrecision (precision) { - this._options.tweenPrecision = precision; - return this; - } - - function growEnteringEdges (enable) { - this._options.growEnteringEdges = enable; - return this; - } - - function on (typenames, callback) { - this._dispatch.on(typenames, callback); - return this; - } - - function onerror (callback) { - this._onerror = callback; - return this; - } - - function logEvents (enable) { - var _this = this; - var t0 = Date.now(); - var times = {}; - var eventTypes = this._eventTypes; - var maxEventTypeLength = Math.max.apply(Math, _toConsumableArray(eventTypes.map(function (eventType) { - return eventType.length; - }))); - var _loop = function _loop(i) { - var eventType = eventTypes[i]; - times[eventType] = []; - graphvizInstance = _this; - _this.on(eventType + '.log', enable ? function () { - var t = Date.now(); - var seqNo = times[eventType].length; - times[eventType].push(t); - var string = ''; - string += 'Event '; - string += d3Format.format(' >2')(i) + ' '; - string += eventType + ' '.repeat(maxEventTypeLength - eventType.length); - string += d3Format.format(' >5')(t - t0) + ' '; - if (eventType != 'initEnd') { - string += d3Format.format(' >5')(t - times['start'][seqNo]); - } - if (eventType == 'dataProcessEnd') { - string += ' prepare ' + d3Format.format(' >5')(t - times['layoutEnd'][seqNo]); - } - if (eventType == 'renderEnd' && graphvizInstance._transition) { - string += ' transition start margin ' + d3Format.format(' >5')(graphvizInstance._transition.delay() - (t - times['renderStart'][seqNo])); - expectedDelay = graphvizInstance._transition.delay(); - expectedDuration = graphvizInstance._transition.duration(); - } - if (eventType == 'transitionStart') { - var actualDelay = t - times['renderStart'][seqNo]; - string += ' transition delay ' + d3Format.format(' >5')(t - times['renderStart'][seqNo]); - string += ' expected ' + d3Format.format(' >5')(expectedDelay); - string += ' diff ' + d3Format.format(' >5')(actualDelay - expectedDelay); - } - if (eventType == 'transitionEnd') { - var actualDuration = t - times['transitionStart'][seqNo]; - string += ' transition duration ' + d3Format.format(' >5')(actualDuration); - string += ' expected ' + d3Format.format(' >5')(expectedDuration); - string += ' diff ' + d3Format.format(' >5')(actualDuration - expectedDuration); - } - console.log(string); - t0 = t; - } : null); - }, - graphvizInstance, - expectedDelay, - expectedDuration; - for (var i = 0; i < eventTypes.length; i++) { - _loop(i); - } - return this; - } - - function destroy () { - delete this._selection.node().__graphviz__; - if (this._worker) { - this._workerPortClose(); - } - return this; - } - - function rotate(x, y, cosA, sinA) { - // (x + j * y) * (cosA + j * sinA) = x * cosA - y * sinA + j * (x * sinA + y * cosA) - y = -y; - sinA = -sinA; - var _ref = [x * cosA - y * sinA, x * sinA + y * cosA]; - x = _ref[0]; - y = _ref[1]; - y = -y; - return [x, y]; - } - - function drawEdge(x1, y1, x2, y2, attributes) { - var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; - attributes = Object.assign({}, attributes); - if (attributes.style && attributes.style.includes('invis')) { - var newEdge = d3__namespace.select(null); - } else { - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var newEdge0 = createEdge.call(this, attributes); - var edgeData = extractAllElementsData(newEdge0); - var newEdge = graph0.append('g').data([edgeData]); - attributeElement.call(newEdge.node(), edgeData); - _updateEdge.call(this, newEdge, x1, y1, x2, y2, attributes, options); - } - this._drawnEdge = { - g: newEdge, - x1: x1, - y1: y1, - x2: x2, - y2: y2, - attributes: attributes - }; - return this; - } - function updateDrawnEdge(x1, y1, x2, y2) { - var attributes = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; - var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; - if (!this._drawnEdge) { - throw Error('No edge has been drawn'); - } - var edge = this._drawnEdge.g; - attributes = Object.assign(this._drawnEdge.attributes, attributes); - this._drawnEdge.x1 = x1; - this._drawnEdge.y1 = y1; - this._drawnEdge.x2 = x2; - this._drawnEdge.y2 = y2; - if (edge.empty() && !(attributes.style && attributes.style.includes('invis'))) { - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var edge = graph0.append('g'); - this._drawnEdge.g = edge; - } - if (!edge.empty()) { - _updateEdge.call(this, edge, x1, y1, x2, y2, attributes, options); - } - return this; - } - function _updateEdge(edge, x1, y1, x2, y2, attributes, options) { - var newEdge = createEdge.call(this, attributes); - var edgeData = extractAllElementsData(newEdge); - edge.data([edgeData]); - attributeElement.call(edge.node(), edgeData); - _moveEdge(edge, x1, y1, x2, y2, attributes, options); - } - function _moveEdge(edge, x1, y1, x2, y2, attributes, options) { - var shortening = options.shortening || 0; - var arrowHeadLength = 10; - var arrowHeadWidth = 7; - var margin = 0.1; - var arrowHeadPoints = [[0, -arrowHeadWidth / 2], [arrowHeadLength, 0], [0, arrowHeadWidth / 2], [0, -arrowHeadWidth / 2]]; - var dx = x2 - x1; - var dy = y2 - y1; - var length = Math.sqrt(dx * dx + dy * dy); - if (length == 0) { - var cosA = 1; - var sinA = 0; - } else { - var cosA = dx / length; - var sinA = dy / length; - } - x2 = x1 + (length - shortening - arrowHeadLength - margin) * cosA; - y2 = y1 + (length - shortening - arrowHeadLength - margin) * sinA; - if (attributes.URL || attributes.tooltip) { - var a = edge.selectWithoutDataPropagation("g").selectWithoutDataPropagation("a"); - var line = a.selectWithoutDataPropagation("path"); - var arrowHead = a.selectWithoutDataPropagation("polygon"); - } else { - var line = edge.selectWithoutDataPropagation("path"); - var arrowHead = edge.selectWithoutDataPropagation("polygon"); - } - var path1 = d3Path.path(); - path1.moveTo(x1, y1); - path1.lineTo(x2, y2); - line.attr("d", path1); - x2 = x1 + (length - shortening - arrowHeadLength) * cosA; - y2 = y1 + (length - shortening - arrowHeadLength) * sinA; - for (var i = 0; i < arrowHeadPoints.length; i++) { - var point = arrowHeadPoints[i]; - arrowHeadPoints[i] = rotate(point[0], point[1], cosA, sinA); - } - for (var i = 0; i < arrowHeadPoints.length; i++) { - var point = arrowHeadPoints[i]; - arrowHeadPoints[i] = [x2 + point[0], y2 + point[1]]; - } - var allPoints = []; - for (var i = 0; i < arrowHeadPoints.length; i++) { - var point = arrowHeadPoints[i]; - allPoints.push(point.join(',')); - } - var pointsAttr = allPoints.join(' '); - arrowHead.attr("points", pointsAttr); - return this; - } - function moveDrawnEdgeEndPoint(x2, y2) { - var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - if (!this._drawnEdge) { - throw Error('No edge has been drawn'); - } - var edge = this._drawnEdge.g; - var x1 = this._drawnEdge.x1; - var y1 = this._drawnEdge.y1; - var attributes = this._drawnEdge.attributes; - this._drawnEdge.x2 = x2; - this._drawnEdge.y2 = y2; - _moveEdge(edge, x1, y1, x2, y2, attributes, options); - return this; - } - function removeDrawnEdge() { - if (!this._drawnEdge) { - return this; - } - var edge = this._drawnEdge.g; - edge.remove(); - this._drawnEdge = null; - return this; - } - function insertDrawnEdge(name) { - if (!this._drawnEdge) { - throw Error('No edge has been drawn'); - } - var edge = this._drawnEdge.g; - if (edge.empty()) { - return this; - } - this._drawnEdge.attributes; - var title = edge.selectWithoutDataPropagation("title"); - title.text(name); - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var graph0Datum = graph0.datum(); - var edgeData = this._extractData(edge, graph0Datum.children.length, graph0.datum()); - graph0Datum.children.push(edgeData); - insertAllElementsData(edge, edgeData); - this._drawnEdge = null; - return this; - } - function drawnEdgeSelection() { - if (this._drawnEdge) { - return this._drawnEdge.g; - } else { - return d3__namespace.select(null); - } - } - function createEdge(attributes) { - var attributesString = ''; - for (var _i = 0, _Object$keys = Object.keys(attributes); _i < _Object$keys.length; _i++) { - var name = _Object$keys[_i]; - if (attributes[name] != null) { - attributesString += ' "' + name + '"="' + attributes[name] + '"'; - } - } - var dotSrc = 'digraph {a -> b [' + attributesString + ']}'; - var svgDoc = this.layoutSync(dotSrc, 'svg', 'dot'); - var parser = new window.DOMParser(); - var doc = parser.parseFromString(svgDoc, "image/svg+xml"); - var newDoc = d3__namespace.select(document.createDocumentFragment()).append(function () { - return doc.documentElement; - }); - var edge = newDoc.select('.edge'); - return edge; - } - - function drawNode(x, y, nodeId) { - var attributes = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; - attributes = Object.assign({}, attributes); - if (attributes.style && attributes.style.includes('invis')) { - var newNode = d3__namespace.select(null); - } else { - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var newNode0 = createNode.call(this, nodeId, attributes); - var nodeData = extractAllElementsData(newNode0); - var newNode = graph0.append('g').data([nodeData]); - attributeElement.call(newNode.node(), nodeData); - _updateNode.call(this, newNode, x, y, nodeId, attributes, options); - } - this._drawnNode = { - g: newNode, - nodeId: nodeId, - x: x, - y: y, - attributes: attributes - }; - return this; - } - function updateDrawnNode(x, y, nodeId) { - var attributes = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; - if (!this._drawnNode) { - throw Error('No node has been drawn'); - } - var node = this._drawnNode.g; - if (nodeId == null) { - nodeId = this._drawnNode.nodeId; - } - attributes = Object.assign(this._drawnNode.attributes, attributes); - this._drawnNode.nodeId = nodeId; - this._drawnNode.x = x; - this._drawnNode.y = y; - if (node.empty() && !(attributes.style && attributes.style.includes('invis'))) { - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var node = graph0.append('g'); - this._drawnNode.g = node; - } - if (!node.empty()) { - _updateNode.call(this, node, x, y, nodeId, attributes, options); - } - return this; - } - function _updateNode(node, x, y, nodeId, attributes, options) { - var newNode = createNode.call(this, nodeId, attributes); - var nodeData = extractAllElementsData(newNode); - node.data([nodeData]); - attributeElement.call(node.node(), nodeData); - _moveNode(node, x, y, attributes); - return this; - } - function _moveNode(node, x, y, attributes, options) { - if (attributes.URL || attributes.tooltip) { - var subParent = node.selectWithoutDataPropagation("g").selectWithoutDataPropagation("a"); - } else { - var subParent = node; - } - var svgElements = subParent.selectAll('ellipse,polygon,path,polyline'); - var text = node.selectWithoutDataPropagation("text"); - if (svgElements.size() != 0) { - var bbox = svgElements.node().getBBox(); - bbox.cx = bbox.x + bbox.width / 2; - bbox.cy = bbox.y + bbox.height / 2; - } else if (text.size() != 0) { - bbox = { - x: +text.attr('x'), - y: +text.attr('y'), - width: 0, - height: 0, - cx: +text.attr('x'), - cy: +text.attr('y') - }; - } - svgElements.each(function (data, index) { - var svgElement = d3__namespace.select(this); - if (svgElement.attr("cx")) { - svgElement.attr("cx", roundTo2Decimals(x)).attr("cy", roundTo2Decimals(y)); - } else if (svgElement.attr("points")) { - var pointsString = svgElement.attr('points').trim(); - svgElement.attr("points", translatePointsAttribute(pointsString, x - bbox.cx, y - bbox.cy)); - } else { - var d = svgElement.attr('d'); - svgElement.attr("d", translateDAttribute(d, x - bbox.cx, y - bbox.cy)); - } - }); - if (text.size() != 0) { - text.attr("x", roundTo2Decimals(+text.attr("x") + x - bbox.cx)).attr("y", roundTo2Decimals(+text.attr("y") + y - bbox.cy)); - } - return this; - } - function moveDrawnNode(x, y) { - if (!this._drawnNode) { - throw Error('No node has been drawn'); - } - var node = this._drawnNode.g; - var attributes = this._drawnNode.attributes; - this._drawnNode.x = x; - this._drawnNode.y = y; - if (!node.empty()) { - _moveNode(node, x, y, attributes); - } - return this; - } - function removeDrawnNode() { - if (!this._drawnNode) { - return this; - } - var node = this._drawnNode.g; - if (!node.empty()) { - node.remove(); - } - this._drawnNode = null; - return this; - } - function insertDrawnNode(nodeId) { - if (!this._drawnNode) { - throw Error('No node has been drawn'); - } - if (nodeId == null) { - nodeId = this._drawnNode.nodeId; - } - var node = this._drawnNode.g; - if (node.empty()) { - return this; - } - var attributes = this._drawnNode.attributes; - var title = node.selectWithoutDataPropagation("title"); - title.text(nodeId); - if (attributes.URL || attributes.tooltip) { - var ga = node.selectWithoutDataPropagation("g"); - var a = ga.selectWithoutDataPropagation("a"); - a.selectWithoutDataPropagation('ellipse,polygon,path,polyline'); - var text = a.selectWithoutDataPropagation('text'); - } else { - node.selectWithoutDataPropagation('ellipse,polygon,path,polyline'); - var text = node.selectWithoutDataPropagation('text'); - } - text.text(attributes.label || nodeId); - var root = this._selection; - var svg = root.selectWithoutDataPropagation("svg"); - var graph0 = svg.selectWithoutDataPropagation("g"); - var graph0Datum = graph0.datum(); - var nodeData = this._extractData(node, graph0Datum.children.length, graph0.datum()); - graph0Datum.children.push(nodeData); - insertAllElementsData(node, nodeData); - this._drawnNode = null; - return this; - } - function drawnNodeSelection() { - if (this._drawnNode) { - return this._drawnNode.g; - } else { - return d3__namespace.select(null); - } - } - function createNode(nodeId, attributes) { - var attributesString = ''; - for (var _i = 0, _Object$keys = Object.keys(attributes); _i < _Object$keys.length; _i++) { - var name = _Object$keys[_i]; - if (attributes[name] != null) { - attributesString += ' "' + name + '"="' + attributes[name] + '"'; - } - } - var dotSrc = 'graph {"' + nodeId + '" [' + attributesString + ']}'; - var svgDoc = this.layoutSync(dotSrc, 'svg', 'dot'); - var parser = new window.DOMParser(); - var doc = parser.parseFromString(svgDoc, "image/svg+xml"); - var newDoc = d3__namespace.select(document.createDocumentFragment()).append(function () { - return doc.documentElement; - }); - var node = newDoc.select('.node'); - return node; - } - - /* This file is excluded from coverage because the instrumented code - * translates "self" which gives a reference error. - */ - - /* c8 ignore start */ - - function workerCodeBody(port) { - self.document = {}; // Workaround for "ReferenceError: document is not defined" in hpccWasm - - port.addEventListener('message', function (event) { - var hpccWasm = self["@hpcc-js/wasm"]; - if (hpccWasm == undefined && event.data.vizURL) { - importScripts(event.data.vizURL); - hpccWasm = self["@hpcc-js/wasm"]; - // This is an alternative workaround where wasmFolder() is not needed - // document = {currentScript: {src: event.data.vizURL}}; - } - if (event.data.type == "version") { - hpccWasm.Graphviz.load().then(function (graphviz) { - port.postMessage({ - type: "version", - version: graphviz.version() - }); - }); - return; - } - hpccWasm.Graphviz.load().then(function (graphviz) { - var svg = graphviz.layout(event.data.dot, "svg", event.data.engine, event.data.options); - if (svg) { - port.postMessage({ - type: "done", - svg: svg - }); - } else if (event.data.vizURL) { - port.postMessage({ - type: "init" - }); - } else { - port.postMessage({ - type: "skip" - }); - } - })["catch"](function (error) { - port.postMessage({ - type: "error", - error: error.message - }); - }); - }); - } - function workerCode() { - var port = self; - workerCodeBody(port); - } - function sharedWorkerCode() { - self.onconnect = function (e) { - var port = e.ports[0]; - workerCodeBody(port); - port.start(); - }; - } - - /* c8 ignore stop */ - - var _graphviz$prototype; - function Graphviz(selection, options) { - var _this = this; - this._options = { - useWorker: true, - useSharedWorker: false, - engine: 'dot', - keyMode: 'title', - fade: true, - tweenPaths: true, - tweenShapes: true, - convertEqualSidedPolygons: true, - tweenPrecision: 1, - growEnteringEdges: true, - zoom: true, - zoomScaleExtent: [0.1, 10], - zoomTranslateExtent: [[-Infinity, -Infinity], [+Infinity, +Infinity]], - width: null, - height: null, - scale: 1, - fit: false - }; - if (options instanceof Object) { - for (var _i = 0, _Object$keys = Object.keys(options); _i < _Object$keys.length; _i++) { - var option = _Object$keys[_i]; - this._options[option] = options[option]; - } - } else if (typeof options == 'boolean') { - this._options.useWorker = options; - } - var useWorker = this._options.useWorker; - var useSharedWorker = this._options.useSharedWorker; - if (typeof Worker == 'undefined') { - useWorker = false; - } - if (typeof SharedWorker == 'undefined') { - useSharedWorker = false; - } - if (useWorker || useSharedWorker) { - var scripts = d3__namespace.selectAll('script'); - var vizScript = scripts.filter(function () { - return d3__namespace.select(this).attr('type') == 'javascript/worker' || d3__namespace.select(this).attr('src') && d3__namespace.select(this).attr('src').match(/.*\/@hpcc-js\/wasm/); - }); - if (vizScript.size() == 0) { - console.warn('No script tag of type "javascript/worker" was found and "useWorker" is true. Not using web worker.'); - useWorker = false; - useSharedWorker = false; - } else { - this._vizURL = vizScript.attr('src'); - if (!this._vizURL) { - console.warn('No "src" attribute of was found on the "javascript/worker" script tag and "useWorker" is true. Not using web worker.'); - useWorker = false; - useSharedWorker = false; - } - } - } - if (useSharedWorker) { - var url = 'data:application/javascript;base64,' + btoa(workerCodeBody.toString() + '(' + sharedWorkerCode.toString() + ')()'); - this._worker = this._worker = new SharedWorker(url); - this._workerPort = this._worker.port; - this._workerPortClose = this._worker.port.close.bind(this._workerPort); - this._worker.port.start(); - this._workerCallbacks = []; - } else if (useWorker) { - var blob = new Blob([workerCodeBody.toString() + '(' + workerCode.toString() + ')()']); - var blobURL = window.URL.createObjectURL(blob); - this._worker = new Worker(blobURL); - this._workerPort = this._worker; - this._workerPortClose = this._worker.terminate.bind(this._worker); - this._workerCallbacks = []; - } else { - xo.load().then(function (graphviz) { - _this._graphvizVersion = graphviz.version(); - }.bind(this)); - } - this._selection = selection; - this._active = false; - this._busy = false; - this._jobs = []; - this._queue = []; - this._keyModes = new Set(['title', 'id', 'tag-index', 'index']); - this._images = []; - this._translation = undefined; - this._scale = undefined; - this._eventTypes = ['initEnd', 'start', 'layoutStart', 'layoutEnd', 'dataExtractEnd', 'dataProcessPass1End', 'dataProcessPass2End', 'dataProcessEnd', 'renderStart', 'renderEnd', 'transitionStart', 'transitionEnd', 'restoreEnd', 'end', 'zoom']; - this._dispatch = d3Dispatch.dispatch.apply(void 0, _toConsumableArray(this._eventTypes)); - initViz.call(this); - selection.node().__graphviz__ = this; - } - function graphviz(selector, options) { - var g = d3__namespace.select(selector).graphviz(options); - return g; - } - Graphviz.prototype = graphviz.prototype = (_graphviz$prototype = { - constructor: Graphviz, - engine: engine, - addImage: images, - keyMode: keyMode, - fade: fade, - tweenPaths: tweenPaths, - tweenShapes: tweenShapes, - convertEqualSidedPolygons: convertEqualSidedPolygons, - tweenPrecision: tweenPrecision, - growEnteringEdges: growEnteringEdges, - zoom: zoom, - resetZoom: resetZoom, - zoomBehavior: zoomBehavior, - zoomSelection: zoomSelection, - zoomScaleExtent: zoomScaleExtent, - zoomTranslateExtent: zoomTranslateExtent, - render: render, - layout: layout, - dot: dot, - data: data, - renderDot: renderDot, - transition: transition, - active: active, - options: options, - width: width, - height: height, - scale: scale, - fit: fit, - attributer: attributer, - on: on, - onerror: onerror, - logEvents: logEvents, - destroy: destroy, - drawEdge: drawEdge, - updateDrawnEdge: updateDrawnEdge, - moveDrawnEdgeEndPoint: moveDrawnEdgeEndPoint, - insertDrawnEdge: insertDrawnEdge, - removeDrawnEdge: removeDrawnEdge - }, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_graphviz$prototype, "removeDrawnEdge", removeDrawnEdge), "drawnEdgeSelection", drawnEdgeSelection), "drawnEdgeSelection", drawnEdgeSelection), "drawNode", drawNode), "updateDrawnNode", updateDrawnNode), "moveDrawnNode", moveDrawnNode), "insertDrawnNode", insertDrawnNode), "removeDrawnNode", removeDrawnNode), "removeDrawnNode", removeDrawnNode), "drawnNodeSelection", drawnNodeSelection), _defineProperty(_defineProperty(_graphviz$prototype, "drawnNodeSelection", drawnNodeSelection), "graphvizVersion", graphvizVersion)); - - function selection_graphviz (options) { - var g = this.node().__graphviz__; - if (g) { - g.options(options); - // Ensure a possible new initEnd event handler is attached before calling it - d3Timer.timeout(function () { - g._dispatch.call("initEnd", this); - }.bind(this), 0); - } else { - g = new Graphviz(this, options); - } - return g; - } - - function selection_selectWithoutDataPropagation (name) { - return d3__namespace.select(this.size() > 0 ? this.node().querySelector(name) : null); - } - - d3.selection.prototype.graphviz = selection_graphviz; - d3.selection.prototype.selectWithoutDataPropagation = selection_selectWithoutDataPropagation; - - exports.graphviz = graphviz; - -})); -//# sourceMappingURL=d3-graphviz.js.map diff --git a/graph/web/vendor/graphviz.umd.js b/graph/web/vendor/graphviz.umd.js deleted file mode 100644 index 622b4d34..00000000 --- a/graph/web/vendor/graphviz.umd.js +++ /dev/null @@ -1,7 +0,0 @@ -(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("@hpcc-js/wasm",f)}else {g["@hpcc-js/wasm"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports}; -"use strict";var pr=Object.defineProperty;var so=Object.getOwnPropertyDescriptor;var uo=Object.getOwnPropertyNames;var po=Object.prototype.hasOwnProperty;var lo=(p,l)=>{for(var d in l)pr(p,d,{get:l[d],enumerable:!0})},co=(p,l,d,i)=>{if(l&&typeof l=="object"||typeof l=="function")for(let f of uo(l))!po.call(p,f)&&f!==d&&pr(p,f,{get:()=>l[f],enumerable:!(i=so(l,f))||i.enumerable});return p};var fo=p=>co(pr({},"__esModule",{value:!0}),p);var Yo={};lo(Yo,{Graphviz:()=>Zo});module.exports=fo(Yo);var vo=ArrayBuffer,H=Uint8Array,Qe=Uint16Array,mo=Int16Array,qe=Int32Array,lr=function(p,l,d){if(H.prototype.slice)return H.prototype.slice.call(p,l,d);(l==null||l<0)&&(l=0),(d==null||d>p.length)&&(d=p.length);var i=new H(d-l);return i.set(p.subarray(l,d)),i},Ye=function(p,l,d,i){if(H.prototype.fill)return H.prototype.fill.call(p,l,d,i);for((d==null||d<0)&&(d=0),(i==null||i>p.length)&&(i=p.length);dp.length)&&(i=p.length);d2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],T=function(p,l,d){var i=new Error(l||go[p]);if(i.code=p,Error.captureStackTrace&&Error.captureStackTrace(i,T),!d)throw i;return i},Pr=function(p,l,d){for(var i=0,f=0;i>>0},wo=function(p,l){var d=p[0]|p[1]<<8|p[2]<<16;if(d==3126568&&p[3]==253){var i=p[4],f=i>>5&1,m=i>>2&1,_=i&3,h=i>>6;i&8&&T(0);var M=6-f,I=_==3?4:_,Q=Pr(p,M,I);M+=I;var Z=h?1<>3);z=q+(q>>3)*(p[5]&7)}z>2145386496&&T(1);var X=new H((l==1?Y||z:l?0:z)+12);return X[0]=1,X[4]=4,X[8]=8,{b:M+Z,y:0,l:0,d:Q,w:l&&l!=1?l:X.subarray(12),e:z,o:new qe(X.buffer,0,3),u:Y,c:m,m:Math.min(131072,z)}}else if((d>>4|p[3]<<20)==25481893)return ho(p,4)+8;T(0)},Xe=function(p){for(var l=0;1<d&&T(3);for(var m=1<0;){var S=Xe(_+1),P=i>>3,ae=(1<>(i&7)&ae,w=(1<w&&(J-=j)),Y[++h]=--J,J==-1?(_+=J,V[--Q]=h):_-=J,!J)do{var ie=i>>3;M=(p[ie]|p[ie+1]<<8)>>(i&7)&3,i+=2,h+=M}while(M==3)}(h>255||_)&&T(0);for(var b=0,k=(m>>1)+(m>>3)+3,se=m-1,oe=0;oe<=h;++oe){var K=Y[oe];if(K<1){z[oe]=-K;continue}for(I=0;I=Q)}}for(b&&T(0),I=0;I>3,{b:f,s:V,n:re,t:q}]},yo=function(p,l){var d=0,i=-1,f=new H(292),m=p[l],_=f.subarray(0,256),h=f.subarray(256,268),M=new Qe(f.buffer,268);if(m<128){var I=je(p,l+1,6),Q=I[0],Z=I[1];l+=m;var Y=Q<<3,z=p[l];z||T(0);for(var q=0,X=0,V=Z.b,re=V,S=(++l<<3)-8+Xe(z);S-=V,!(S>3;if(q+=(p[P]|p[P+1]<<8)>>(S&7)&(1<>3,X+=(p[P]|p[P+1]<<8)>>(S&7)&(1<255&&T(0)}else{for(i=m-127;d>4,_[d+1]=ae&15}++l}var J=0;for(d=0;d11&&T(0),J+=w&&1<0;--d){var oe=M[d];Ye(se,d,oe,M[d-1]=oe+h[d]*(1<h&&Z>3,z=(p[Y]|p[Y+1]<<8|p[Y+2]<<16)>>(Q&7);M=(M<>2,_=m<<1,h=m+_;Ze(p.subarray(i,i+=p[0]|p[1]<<8),l.subarray(0,m),d),Ze(p.subarray(i,i+=p[2]|p[3]<<8),l.subarray(m,_),d),Ze(p.subarray(i,i+=p[4]|p[5]<<8),l.subarray(_,h),d),Ze(p.subarray(i),l.subarray(h),d)},Bo=function(p,l,d){var i,f=l.b,m=p[f],_=m>>1&3;l.l=m&1;var h=m>>3|p[f+1]<<5|p[f+2]<<13,M=(f+=3)+h;if(_==1)return f>=p.length?void 0:(l.b=f+1,d?(Ye(d,p[f],l.y,l.y+=h),d):Ye(new H(h),p[f]));if(!(M>p.length)){if(_==0)return l.b=M,d?(d.set(p.subarray(f,M),l.y),l.y+=h,d):lr(p,f,M);if(_==2){var I=p[f],Q=I&3,Z=I>>2&3,Y=I>>4,z=0,q=0;Q<2?Z&1?Y|=p[++f]<<4|(Z&2&&p[++f]<<12):Y=I>>3:(q=Z,Z<2?(Y|=(p[++f]&63)<<4,z=p[f]>>6|p[++f]<<2):Z==2?(Y|=p[++f]<<4|(p[++f]&3)<<12,z=p[f]>>2|p[++f]<<6):(Y|=p[++f]<<4|(p[++f]&63)<<12,z=p[f]>>6|p[++f]<<2|p[++f]<<10)),++f;var X=d?d.subarray(l.y,l.y+l.m):new H(l.m),V=X.length-Y;if(Q==0)X.set(p.subarray(f,f+=Y),V);else if(Q==1)Ye(X,p[f++],V);else{var re=l.h;if(Q==2){var S=yo(p,f);z+=f-(f=S[0]),l.h=re=S[1]}else re||T(0);(q?Lo:Ze)(p.subarray(f,f+=z),X.subarray(V),re)}var P=p[f++];if(P){P==255?P=(p[f++]|p[f++]<<8)+32512:P>127&&(P=P-128<<8|p[f++]);var ae=p[f++];ae&3&&T(0);for(var J=[Eo,Mo,bo],w=2;w>-1;--w){var j=ae>>(w<<1)+2&3;if(j==1){var W=new H([0,0,p[f++]]);J[w]={s:W.subarray(2,3),n:W.subarray(0,1),t:new Qe(W.buffer,0,1),b:0}}else j==2?(i=je(p,f,9-(w&1)),f=i[0],J[w]=i[1]):j==3&&(l.t||T(0),J[w]=l.t[w])}var ie=l.t=J,b=ie[0],k=ie[1],se=ie[2],oe=p[M-1];oe||T(0);var K=(M<<3)-8+Xe(oe)-se.b,L=K>>3,A=0,Le=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var Be=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var Re=(p[L]|p[L+1]<<8)>>(K&7)&(1<>3;var $e=1<>>(K&7)&$e-1);L=(K-=fr[Ve])>>3;var me=Xo[Ve]+((p[L]|p[L+1]<<8|p[L+2]<<16)>>(K&7)&(1<>3;var _e=Do[Ne]+((p[L]|p[L+1]<<8|p[L+2]<<16)>>(K&7)&(1<>3,Le=se.t[Le]+((p[L]|p[L+1]<<8)>>(K&7)&(1<>3,Re=b.t[Re]+((p[L]|p[L+1]<<8)>>(K&7)&(1<>3,Be=k.t[Be]+((p[L]|p[L+1]<<8)>>(K&7)&(1<3)l.o[2]=l.o[1],l.o[1]=l.o[0],l.o[0]=ce-=3;else{var Ge=ce-(_e!=0);Ge?(ce=Ge==3?l.o[0]-1:l.o[Ge],Ge>1&&(l.o[2]=l.o[1]),l.o[1]=l.o[0],l.o[0]=ce):ce=l.o[0]}for(var w=0;w<_e;++w)X[A+w]=X[V+w];A+=_e,V+=_e;var Ee=A-ce;if(Ee<0){var De=-Ee,We=l.e+Ee;De>me&&(De=me);for(var w=0;w{var p=typeof document<"u"?document.currentScript?.src:void 0;return function(l={}){var d,i=l,f,m,_=new Promise((e,r)=>{f=e,m=r}),h=!1,M=Object.assign({},i),I="./this.program",Q=(e,r)=>{throw r},Z="";function Y(e){return Z+e}var z,q,X=console.log.bind(console),V=console.error.bind(console);Object.assign(i,M),M=null;var re;i.wasmBinary&&(re=i.wasmBinary);var S,P=!1,ae;function J(e,r){e||_e(r)}var w,j,W,ie,b,k,se,oe;function K(){var e=S.buffer;i.HEAP8=w=new Int8Array(e),i.HEAP16=W=new Int16Array(e),i.HEAPU8=j=new Uint8Array(e),i.HEAPU16=ie=new Uint16Array(e),i.HEAP32=b=new Int32Array(e),i.HEAPU32=k=new Uint32Array(e),i.HEAPF32=se=new Float32Array(e),i.HEAPF64=oe=new Float64Array(e)}var L=[],A=[],Le=[],Be=!1;function Re(){Ae(L)}function Ne(){Be=!0,!i.noFSInit&&!o.init.initialized&&o.init(),o.ignorePermissions=!1,Me.init(),Ae(A)}function Pe(){Ae(Le)}function Ve(e){A.unshift(e)}var ye=0,ke=null,be=null;function $e(e){return e}function ce(e){ye++}function me(e){if(ye--,ye==0&&(ke!==null&&(clearInterval(ke),ke=null),be)){var r=be;be=null,r()}}function _e(e){e="Aborted("+e+")",V(e),P=!0,ae=1,e+=". Build with -sASSERTIONS for more info.";var r=new WebAssembly.RuntimeError(e);throw m(r),r}var Ge="data:application/octet-stream;base64,",Ee=e=>e.startsWith(Ge),De=e=>e.startsWith("file://");function We(){var e="graphvizlib.wasm";return Ee(e)?e:Y(e)}var Je;function vr(e){if(e==Je&&re)return new Uint8Array(re);if(q)return q(e);throw"both async and sync fetching of the wasm failed"}function Sr(e){return re?Promise.resolve().then(()=>vr(e)):z(e).then(r=>new Uint8Array(r),()=>vr(e))}function mr(e,r,t){return Sr(e).then(n=>WebAssembly.instantiate(n,r)).then(t,n=>{V(`failed to asynchronously prepare wasm: ${n}`),_e(n)})}function Hr(e,r,t,n){return!e&&typeof WebAssembly.instantiateStreaming=="function"&&!Ee(r)&&!De(r)&&typeof fetch=="function"?fetch(r,{credentials:"same-origin"}).then(a=>{var s=WebAssembly.instantiateStreaming(a,t);return s.then(n,function(u){return V(`wasm streaming compile failed: ${u}`),V("falling back to ArrayBuffer instantiation"),mr(r,t,n)})}):mr(r,t,n)}function Tr(){return{a:qt}}function Qr(){var e=Tr();function r(n,a){return N=n.exports,S=N.B,K(),Ve(N.C),me("wasm-instantiate"),N}ce("wasm-instantiate");function t(n){r(n.instance)}return Je||(Je=We()),Hr(re,Je,e,t).catch(m),{}}var R,$,qr={172808:(e,r)=>{var t=ue(e),n=ue(r);o.createPath("/",C.dirname(t)),o.writeFile(C.join("/",t),n)}};function $r(e){this.name="ExitStatus",this.message=`Program terminated with exit(${e})`,this.status=e}var Ae=e=>{for(;e.length>0;)e.shift()(i)};class Wr{constructor(r){this.excPtr=r,this.ptr=r-24}set_type(r){k[this.ptr+4>>2]=r}get_type(){return k[this.ptr+4>>2]}set_destructor(r){k[this.ptr+8>>2]=r}get_destructor(){return k[this.ptr+8>>2]}set_caught(r){r=r?1:0,w[this.ptr+12]=r}get_caught(){return w[this.ptr+12]!=0}set_rethrown(r){r=r?1:0,w[this.ptr+13]=r}get_rethrown(){return w[this.ptr+13]!=0}init(r,t){this.set_adjusted_ptr(0),this.set_type(r),this.set_destructor(t)}set_adjusted_ptr(r){k[this.ptr+16>>2]=r}get_adjusted_ptr(){return k[this.ptr+16>>2]}get_exception_ptr(){var r=zr(this.get_type());if(r)return k[this.excPtr>>2];var t=this.get_adjusted_ptr();return t!==0?t:this.excPtr}}var _r=0,Ar=0,et=(e,r,t)=>{var n=new Wr(e);throw n.init(r,t),_r=e,Ar++,_r},C={isAbs:e=>e.charAt(0)==="/",splitPath:e=>{var r=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return r.exec(e).slice(1)},normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var a=e[n];a==="."?e.splice(n,1):a===".."?(e.splice(n,1),t++):t&&(e.splice(n,1),t--)}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=C.isAbs(e),t=e.substr(-1)==="/";return e=C.normalizeArray(e.split("/").filter(n=>!!n),!r).join("/"),!e&&!r&&(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=C.splitPath(e),t=r[0],n=r[1];return!t&&!n?".":(n&&(n=n.substr(0,n.length-1)),t+n)},basename:e=>{if(e==="/")return"/";e=C.normalize(e),e=e.replace(/\/$/,"");var r=e.lastIndexOf("/");return r===-1?e:e.substr(r+1)},join:(...e)=>C.normalize(e.join("/")),join2:(e,r)=>C.normalize(e+"/"+r)},rt=()=>{if(typeof crypto=="object"&&typeof crypto.getRandomValues=="function")return e=>crypto.getRandomValues(e);_e("initRandomDevice")},gr=e=>(gr=rt())(e),he={resolve:(...e)=>{for(var r="",t=!1,n=e.length-1;n>=-1&&!t;n--){var a=n>=0?e[n]:o.cwd();if(typeof a!="string")throw new TypeError("Arguments to path.resolve must be strings");if(!a)return"";r=a+"/"+r,t=C.isAbs(a)}return r=C.normalizeArray(r.split("/").filter(s=>!!s),!t).join("/"),(t?"/":"")+r||"."},relative:(e,r)=>{e=he.resolve(e).substr(1),r=he.resolve(r).substr(1);function t(E){for(var G=0;G=0&&E[x]==="";x--);return G>x?[]:E.slice(G,x-G+1)}for(var n=t(e.split("/")),a=t(r.split("/")),s=Math.min(n.length,a.length),u=s,c=0;c{for(var n=r+t,a=r;e[a]&&!(a>=n);)++a;if(a-r>16&&e.buffer&&hr)return hr.decode(e.subarray(r,a));for(var s="";r>10,56320|E&1023)}}return s},er=[],rr=e=>{for(var r=0,t=0;t=55296&&n<=57343?(r+=4,++t):r+=3}return r},tr=(e,r,t,n)=>{if(!(n>0))return 0;for(var a=t,s=t+n-1,u=0;u=55296&&c<=57343){var v=e.charCodeAt(++u);c=65536+((c&1023)<<10)|v&1023}if(c<=127){if(t>=s)break;r[t++]=c}else if(c<=2047){if(t+1>=s)break;r[t++]=192|c>>6,r[t++]=128|c&63}else if(c<=65535){if(t+2>=s)break;r[t++]=224|c>>12,r[t++]=128|c>>6&63,r[t++]=128|c&63}else{if(t+3>=s)break;r[t++]=240|c>>18,r[t++]=128|c>>12&63,r[t++]=128|c>>6&63,r[t++]=128|c&63}}return r[t]=0,t-a};function or(e,r,t){var n=t>0?t:rr(e)+1,a=new Array(n),s=tr(e,a,0,a.length);return r&&(a.length=s),a}var tt=()=>{if(!er.length){var e=null;if(!e)return null;er=or(e,!0)}return er.shift()},Me={ttys:[],init(){},shutdown(){},register(e,r){Me.ttys[e]={input:[],output:[],ops:r},o.registerDevice(e,Me.stream_ops)},stream_ops:{open(e){var r=Me.ttys[e.node.rdev];if(!r)throw new o.ErrnoError(43);e.tty=r,e.seekable=!1},close(e){e.tty.ops.fsync(e.tty)},fsync(e){e.tty.ops.fsync(e.tty)},read(e,r,t,n,a){if(!e.tty||!e.tty.ops.get_char)throw new o.ErrnoError(60);for(var s=0,u=0;u0&&(X(Fe(e.output,0)),e.output=[])},ioctl_tcgets(e){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(e,r,t){return 0},ioctl_tiocgwinsz(e){return[24,80]}},default_tty1_ops:{put_char(e,r){r===null||r===10?(V(Fe(e.output,0)),e.output=[]):r!=0&&e.output.push(r)},fsync(e){e.output&&e.output.length>0&&(V(Fe(e.output,0)),e.output=[])}}},ot=(e,r)=>(j.fill(0,e,e+r),e),wr=(e,r)=>Math.ceil(e/r)*r,yr=e=>{e=wr(e,65536);var r=Yr(65536,e);return r?ot(r,e):0},D={ops_table:null,mount(e){return D.createNode(null,"/",16895,0)},createNode(e,r,t,n){if(o.isBlkdev(t)||o.isFIFO(t))throw new o.ErrnoError(63);D.ops_table||={dir:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr,lookup:D.node_ops.lookup,mknod:D.node_ops.mknod,rename:D.node_ops.rename,unlink:D.node_ops.unlink,rmdir:D.node_ops.rmdir,readdir:D.node_ops.readdir,symlink:D.node_ops.symlink},stream:{llseek:D.stream_ops.llseek}},file:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr},stream:{llseek:D.stream_ops.llseek,read:D.stream_ops.read,write:D.stream_ops.write,allocate:D.stream_ops.allocate,mmap:D.stream_ops.mmap,msync:D.stream_ops.msync}},link:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr,readlink:D.node_ops.readlink},stream:{}},chrdev:{node:{getattr:D.node_ops.getattr,setattr:D.node_ops.setattr},stream:o.chrdev_stream_ops}};var a=o.createNode(e,r,t,n);return o.isDir(a.mode)?(a.node_ops=D.ops_table.dir.node,a.stream_ops=D.ops_table.dir.stream,a.contents={}):o.isFile(a.mode)?(a.node_ops=D.ops_table.file.node,a.stream_ops=D.ops_table.file.stream,a.usedBytes=0,a.contents=null):o.isLink(a.mode)?(a.node_ops=D.ops_table.link.node,a.stream_ops=D.ops_table.link.stream):o.isChrdev(a.mode)&&(a.node_ops=D.ops_table.chrdev.node,a.stream_ops=D.ops_table.chrdev.stream),a.timestamp=Date.now(),e&&(e.contents[r]=a,e.timestamp=a.timestamp),a},getFileDataAsTypedArray(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){var n=1048576;r=Math.max(r,t*(t>>0),t!=0&&(r=Math.max(r,256));var a=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(a.subarray(0,e.usedBytes),0)}},resizeFileStorage(e,r){if(e.usedBytes!=r)if(r==0)e.contents=null,e.usedBytes=0;else{var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r}},node_ops:{getattr(e){var r={};return r.dev=o.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,o.isDir(e.mode)?r.size=4096:o.isFile(e.mode)?r.size=e.usedBytes:o.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr(e,r){r.mode!==void 0&&(e.mode=r.mode),r.timestamp!==void 0&&(e.timestamp=r.timestamp),r.size!==void 0&&D.resizeFileStorage(e,r.size)},lookup(e,r){throw o.genericErrors[44]},mknod(e,r,t,n){return D.createNode(e,r,t,n)},rename(e,r,t){if(o.isDir(e.mode)){var n;try{n=o.lookupNode(r,t)}catch{}if(n)for(var a in n.contents)throw new o.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp},unlink(e,r){delete e.contents[r],e.timestamp=Date.now()},rmdir(e,r){var t=o.lookupNode(e,r);for(var n in t.contents)throw new o.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now()},readdir(e){var r=[".",".."];for(var t of Object.keys(e.contents))r.push(t);return r},symlink(e,r,t){var n=D.createNode(e,r,41471,0);return n.link=t,n},readlink(e){if(!o.isLink(e.mode))throw new o.ErrnoError(28);return e.link}},stream_ops:{read(e,r,t,n,a){var s=e.node.contents;if(a>=e.node.usedBytes)return 0;var u=Math.min(e.node.usedBytes-a,n);if(u>8&&s.subarray)r.set(s.subarray(a,a+u),t);else for(var c=0;c0||t+r{var a=n?"":`al ${e}`;z(e).then(s=>{r(new Uint8Array(s)),a&&me(a)},s=>{if(t)t();else throw`Loading data file "${e}" failed.`}),a&&ce(a)},at=(e,r,t,n,a,s)=>{o.createDataFile(e,r,t,n,a,s)},it=[],st=(e,r,t,n)=>{typeof Browser<"u"&&Browser.init();var a=!1;return it.forEach(s=>{a||s.canHandle(r)&&(s.handle(e,r,t,n),a=!0)}),a},ut=(e,r,t,n,a,s,u,c,v,E)=>{var G=r?he.resolve(C.join2(e,r)):e,x=`cp ${G}`;function B(y){function O(te){E?.(),c||at(e,r,te,n,a,v),s?.(),me(x)}st(y,G,O,()=>{u?.(),me(x)})||O(y)}ce(x),typeof t=="string"?nt(t,B,u):B(t)},pt=e=>{var r={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},t=r[e];if(typeof t>"u")throw new Error(`Unknown file open mode: ${e}`);return t},nr=(e,r)=>{var t=0;return e&&(t|=365),r&&(t|=146),t},o={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:!1,ignorePermissions:!0,ErrnoError:class{constructor(e){this.name="ErrnoError",this.errno=e}},genericErrors:{},filesystems:null,syncFSRequests:0,FSStream:class{constructor(){this.shared={}}get object(){return this.node}set object(e){this.node=e}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(e){this.shared.flags=e}get position(){return this.shared.position}set position(e){this.shared.position=e}},FSNode:class{constructor(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=o.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n,this.readMode=365,this.writeMode=146}get read(){return(this.mode&this.readMode)===this.readMode}set read(e){e?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(e){e?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return o.isDir(this.mode)}get isDevice(){return o.isChrdev(this.mode)}},lookupPath(e,r={}){if(e=he.resolve(e),!e)return{path:"",node:null};var t={follow_mount:!0,recurse_count:0};if(r=Object.assign(t,r),r.recurse_count>8)throw new o.ErrnoError(32);for(var n=e.split("/").filter(x=>!!x),a=o.root,s="/",u=0;u40)throw new o.ErrnoError(32)}}return{path:s,node:a}},getPath(e){for(var r;;){if(o.isRoot(e)){var t=e.mount.mountpoint;return r?t[t.length-1]!=="/"?`${t}/${r}`:t+r:t}r=r?`${e.name}/${r}`:e.name,e=e.parent}},hashName(e,r){for(var t=0,n=0;n>>0)%o.nameTable.length},hashAddNode(e){var r=o.hashName(e.parent.id,e.name);e.name_next=o.nameTable[r],o.nameTable[r]=e},hashRemoveNode(e){var r=o.hashName(e.parent.id,e.name);if(o.nameTable[r]===e)o.nameTable[r]=e.name_next;else for(var t=o.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next}},lookupNode(e,r){var t=o.mayLookup(e);if(t)throw new o.ErrnoError(t);for(var n=o.hashName(e.id,r),a=o.nameTable[n];a;a=a.name_next){var s=a.name;if(a.parent.id===e.id&&s===r)return a}return o.lookup(e,r)},createNode(e,r,t,n){var a=new o.FSNode(e,r,t,n);return o.hashAddNode(a),a},destroyNode(e){o.hashRemoveNode(e)},isRoot(e){return e===e.parent},isMountpoint(e){return!!e.mounted},isFile(e){return(e&61440)===32768},isDir(e){return(e&61440)===16384},isLink(e){return(e&61440)===40960},isChrdev(e){return(e&61440)===8192},isBlkdev(e){return(e&61440)===24576},isFIFO(e){return(e&61440)===4096},isSocket(e){return(e&49152)===49152},flagsToPermissionString(e){var r=["r","w","rw"][e&3];return e&512&&(r+="w"),r},nodePermissions(e,r){return o.ignorePermissions?0:r.includes("r")&&!(e.mode&292)||r.includes("w")&&!(e.mode&146)||r.includes("x")&&!(e.mode&73)?2:0},mayLookup(e){if(!o.isDir(e.mode))return 54;var r=o.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate(e,r){try{var t=o.lookupNode(e,r);return 20}catch{}return o.nodePermissions(e,"wx")},mayDelete(e,r,t){var n;try{n=o.lookupNode(e,r)}catch(s){return s.errno}var a=o.nodePermissions(e,"wx");if(a)return a;if(t){if(!o.isDir(n.mode))return 54;if(o.isRoot(n)||o.getPath(n)===o.cwd())return 10}else if(o.isDir(n.mode))return 31;return 0},mayOpen(e,r){return e?o.isLink(e.mode)?32:o.isDir(e.mode)&&(o.flagsToPermissionString(r)!=="r"||r&512)?31:o.nodePermissions(e,o.flagsToPermissionString(r)):44},MAX_OPEN_FDS:4096,nextfd(){for(var e=0;e<=o.MAX_OPEN_FDS;e++)if(!o.streams[e])return e;throw new o.ErrnoError(33)},getStreamChecked(e){var r=o.getStream(e);if(!r)throw new o.ErrnoError(8);return r},getStream:e=>o.streams[e],createStream(e,r=-1){return e=Object.assign(new o.FSStream,e),r==-1&&(r=o.nextfd()),e.fd=r,o.streams[r]=e,e},closeStream(e){o.streams[e]=null},dupStream(e,r=-1){var t=o.createStream(e,r);return t.stream_ops?.dup?.(t),t},chrdev_stream_ops:{open(e){var r=o.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open?.(e)},llseek(){throw new o.ErrnoError(70)}},major:e=>e>>8,minor:e=>e&255,makedev:(e,r)=>e<<8|r,registerDevice(e,r){o.devices[e]={stream_ops:r}},getDevice:e=>o.devices[e],getMounts(e){for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push(...n.mounts)}return r},syncfs(e,r){typeof e=="function"&&(r=e,e=!1),o.syncFSRequests++,o.syncFSRequests>1&&V(`warning: ${o.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`);var t=o.getMounts(o.root.mount),n=0;function a(u){return o.syncFSRequests--,r(u)}function s(u){if(u)return s.errored?void 0:(s.errored=!0,a(u));++n>=t.length&&a(null)}t.forEach(u=>{if(!u.type.syncfs)return s(null);u.type.syncfs(u,e,s)})},mount(e,r,t){var n=t==="/",a=!t,s;if(n&&o.root)throw new o.ErrnoError(10);if(!n&&!a){var u=o.lookupPath(t,{follow_mount:!1});if(t=u.path,s=u.node,o.isMountpoint(s))throw new o.ErrnoError(10);if(!o.isDir(s.mode))throw new o.ErrnoError(54)}var c={type:e,opts:r,mountpoint:t,mounts:[]},v=e.mount(c);return v.mount=c,c.root=v,n?o.root=v:s&&(s.mounted=c,s.mount&&s.mount.mounts.push(c)),v},unmount(e){var r=o.lookupPath(e,{follow_mount:!1});if(!o.isMountpoint(r.node))throw new o.ErrnoError(28);var t=r.node,n=t.mounted,a=o.getMounts(n);Object.keys(o.nameTable).forEach(u=>{for(var c=o.nameTable[u];c;){var v=c.name_next;a.includes(c.mount)&&o.destroyNode(c),c=v}}),t.mounted=null;var s=t.mount.mounts.indexOf(n);t.mount.mounts.splice(s,1)},lookup(e,r){return e.node_ops.lookup(e,r)},mknod(e,r,t){var n=o.lookupPath(e,{parent:!0}),a=n.node,s=C.basename(e);if(!s||s==="."||s==="..")throw new o.ErrnoError(28);var u=o.mayCreate(a,s);if(u)throw new o.ErrnoError(u);if(!a.node_ops.mknod)throw new o.ErrnoError(63);return a.node_ops.mknod(a,s,r,t)},create(e,r){return r=r!==void 0?r:438,r&=4095,r|=32768,o.mknod(e,r,0)},mkdir(e,r){return r=r!==void 0?r:511,r&=1023,r|=16384,o.mknod(e,r,0)},mkdirTree(e,r){for(var t=e.split("/"),n="",a=0;a"u"&&(t=r,r=438),r|=8192,o.mknod(e,r,t)},symlink(e,r){if(!he.resolve(e))throw new o.ErrnoError(44);var t=o.lookupPath(r,{parent:!0}),n=t.node;if(!n)throw new o.ErrnoError(44);var a=C.basename(r),s=o.mayCreate(n,a);if(s)throw new o.ErrnoError(s);if(!n.node_ops.symlink)throw new o.ErrnoError(63);return n.node_ops.symlink(n,a,e)},rename(e,r){var t=C.dirname(e),n=C.dirname(r),a=C.basename(e),s=C.basename(r),u,c,v;if(u=o.lookupPath(e,{parent:!0}),c=u.node,u=o.lookupPath(r,{parent:!0}),v=u.node,!c||!v)throw new o.ErrnoError(44);if(c.mount!==v.mount)throw new o.ErrnoError(75);var E=o.lookupNode(c,a),G=he.relative(e,n);if(G.charAt(0)!==".")throw new o.ErrnoError(28);if(G=he.relative(r,t),G.charAt(0)!==".")throw new o.ErrnoError(55);var x;try{x=o.lookupNode(v,s)}catch{}if(E!==x){var B=o.isDir(E.mode),y=o.mayDelete(c,a,B);if(y)throw new o.ErrnoError(y);if(y=x?o.mayDelete(v,s,B):o.mayCreate(v,s),y)throw new o.ErrnoError(y);if(!c.node_ops.rename)throw new o.ErrnoError(63);if(o.isMountpoint(E)||x&&o.isMountpoint(x))throw new o.ErrnoError(10);if(v!==c&&(y=o.nodePermissions(c,"w"),y))throw new o.ErrnoError(y);o.hashRemoveNode(E);try{c.node_ops.rename(E,v,s),E.parent=v}catch(O){throw O}finally{o.hashAddNode(E)}}},rmdir(e){var r=o.lookupPath(e,{parent:!0}),t=r.node,n=C.basename(e),a=o.lookupNode(t,n),s=o.mayDelete(t,n,!0);if(s)throw new o.ErrnoError(s);if(!t.node_ops.rmdir)throw new o.ErrnoError(63);if(o.isMountpoint(a))throw new o.ErrnoError(10);t.node_ops.rmdir(t,n),o.destroyNode(a)},readdir(e){var r=o.lookupPath(e,{follow:!0}),t=r.node;if(!t.node_ops.readdir)throw new o.ErrnoError(54);return t.node_ops.readdir(t)},unlink(e){var r=o.lookupPath(e,{parent:!0}),t=r.node;if(!t)throw new o.ErrnoError(44);var n=C.basename(e),a=o.lookupNode(t,n),s=o.mayDelete(t,n,!1);if(s)throw new o.ErrnoError(s);if(!t.node_ops.unlink)throw new o.ErrnoError(63);if(o.isMountpoint(a))throw new o.ErrnoError(10);t.node_ops.unlink(t,n),o.destroyNode(a)},readlink(e){var r=o.lookupPath(e),t=r.node;if(!t)throw new o.ErrnoError(44);if(!t.node_ops.readlink)throw new o.ErrnoError(28);return he.resolve(o.getPath(t.parent),t.node_ops.readlink(t))},stat(e,r){var t=o.lookupPath(e,{follow:!r}),n=t.node;if(!n)throw new o.ErrnoError(44);if(!n.node_ops.getattr)throw new o.ErrnoError(63);return n.node_ops.getattr(n)},lstat(e){return o.stat(e,!0)},chmod(e,r,t){var n;if(typeof e=="string"){var a=o.lookupPath(e,{follow:!t});n=a.node}else n=e;if(!n.node_ops.setattr)throw new o.ErrnoError(63);n.node_ops.setattr(n,{mode:r&4095|n.mode&-4096,timestamp:Date.now()})},lchmod(e,r){o.chmod(e,r,!0)},fchmod(e,r){var t=o.getStreamChecked(e);o.chmod(t.node,r)},chown(e,r,t,n){var a;if(typeof e=="string"){var s=o.lookupPath(e,{follow:!n});a=s.node}else a=e;if(!a.node_ops.setattr)throw new o.ErrnoError(63);a.node_ops.setattr(a,{timestamp:Date.now()})},lchown(e,r,t){o.chown(e,r,t,!0)},fchown(e,r,t){var n=o.getStreamChecked(e);o.chown(n.node,r,t)},truncate(e,r){if(r<0)throw new o.ErrnoError(28);var t;if(typeof e=="string"){var n=o.lookupPath(e,{follow:!0});t=n.node}else t=e;if(!t.node_ops.setattr)throw new o.ErrnoError(63);if(o.isDir(t.mode))throw new o.ErrnoError(31);if(!o.isFile(t.mode))throw new o.ErrnoError(28);var a=o.nodePermissions(t,"w");if(a)throw new o.ErrnoError(a);t.node_ops.setattr(t,{size:r,timestamp:Date.now()})},ftruncate(e,r){var t=o.getStreamChecked(e);if(!(t.flags&2097155))throw new o.ErrnoError(28);o.truncate(t.node,r)},utime(e,r,t){var n=o.lookupPath(e,{follow:!0}),a=n.node;a.node_ops.setattr(a,{timestamp:Math.max(r,t)})},open(e,r,t){if(e==="")throw new o.ErrnoError(44);r=typeof r=="string"?pt(r):r,r&64?(t=typeof t>"u"?438:t,t=t&4095|32768):t=0;var n;if(typeof e=="object")n=e;else{e=C.normalize(e);try{var a=o.lookupPath(e,{follow:!(r&131072)});n=a.node}catch{}}var s=!1;if(r&64)if(n){if(r&128)throw new o.ErrnoError(20)}else n=o.mknod(e,t,0),s=!0;if(!n)throw new o.ErrnoError(44);if(o.isChrdev(n.mode)&&(r&=-513),r&65536&&!o.isDir(n.mode))throw new o.ErrnoError(54);if(!s){var u=o.mayOpen(n,r);if(u)throw new o.ErrnoError(u)}r&512&&!s&&o.truncate(n,0),r&=-131713;var c=o.createStream({node:n,path:o.getPath(n),flags:r,seekable:!0,position:0,stream_ops:n.stream_ops,ungotten:[],error:!1});return c.stream_ops.open&&c.stream_ops.open(c),i.logReadFiles&&!(r&1)&&(o.readFiles||(o.readFiles={}),e in o.readFiles||(o.readFiles[e]=1)),c},close(e){if(o.isClosed(e))throw new o.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e)}catch(r){throw r}finally{o.closeStream(e.fd)}e.fd=null},isClosed(e){return e.fd===null},llseek(e,r,t){if(o.isClosed(e))throw new o.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new o.ErrnoError(70);if(t!=0&&t!=1&&t!=2)throw new o.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read(e,r,t,n,a){if(n<0||a<0)throw new o.ErrnoError(28);if(o.isClosed(e))throw new o.ErrnoError(8);if((e.flags&2097155)===1)throw new o.ErrnoError(8);if(o.isDir(e.node.mode))throw new o.ErrnoError(31);if(!e.stream_ops.read)throw new o.ErrnoError(28);var s=typeof a<"u";if(!s)a=e.position;else if(!e.seekable)throw new o.ErrnoError(70);var u=e.stream_ops.read(e,r,t,n,a);return s||(e.position+=u),u},write(e,r,t,n,a,s){if(n<0||a<0)throw new o.ErrnoError(28);if(o.isClosed(e))throw new o.ErrnoError(8);if(!(e.flags&2097155))throw new o.ErrnoError(8);if(o.isDir(e.node.mode))throw new o.ErrnoError(31);if(!e.stream_ops.write)throw new o.ErrnoError(28);e.seekable&&e.flags&1024&&o.llseek(e,0,2);var u=typeof a<"u";if(!u)a=e.position;else if(!e.seekable)throw new o.ErrnoError(70);var c=e.stream_ops.write(e,r,t,n,a,s);return u||(e.position+=c),c},allocate(e,r,t){if(o.isClosed(e))throw new o.ErrnoError(8);if(r<0||t<=0)throw new o.ErrnoError(28);if(!(e.flags&2097155))throw new o.ErrnoError(8);if(!o.isFile(e.node.mode)&&!o.isDir(e.node.mode))throw new o.ErrnoError(43);if(!e.stream_ops.allocate)throw new o.ErrnoError(138);e.stream_ops.allocate(e,r,t)},mmap(e,r,t,n,a){if(n&2&&!(a&2)&&(e.flags&2097155)!==2)throw new o.ErrnoError(2);if((e.flags&2097155)===1)throw new o.ErrnoError(2);if(!e.stream_ops.mmap)throw new o.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,a)},msync(e,r,t,n,a){return e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,a):0},ioctl(e,r,t){if(!e.stream_ops.ioctl)throw new o.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile(e,r={}){if(r.flags=r.flags||0,r.encoding=r.encoding||"binary",r.encoding!=="utf8"&&r.encoding!=="binary")throw new Error(`Invalid encoding type "${r.encoding}"`);var t,n=o.open(e,r.flags),a=o.stat(e),s=a.size,u=new Uint8Array(s);return o.read(n,u,0,s,0),r.encoding==="utf8"?t=Fe(u,0):r.encoding==="binary"&&(t=u),o.close(n),t},writeFile(e,r,t={}){t.flags=t.flags||577;var n=o.open(e,t.flags,t.mode);if(typeof r=="string"){var a=new Uint8Array(rr(r)+1),s=tr(r,a,0,a.length);o.write(n,a,0,s,void 0,t.canOwn)}else if(ArrayBuffer.isView(r))o.write(n,r,0,r.byteLength,void 0,t.canOwn);else throw new Error("Unsupported data type");o.close(n)},cwd:()=>o.currentPath,chdir(e){var r=o.lookupPath(e,{follow:!0});if(r.node===null)throw new o.ErrnoError(44);if(!o.isDir(r.node.mode))throw new o.ErrnoError(54);var t=o.nodePermissions(r.node,"x");if(t)throw new o.ErrnoError(t);o.currentPath=r.path},createDefaultDirectories(){o.mkdir("/tmp"),o.mkdir("/home"),o.mkdir("/home/web_user")},createDefaultDevices(){o.mkdir("/dev"),o.registerDevice(o.makedev(1,3),{read:()=>0,write:(n,a,s,u,c)=>u}),o.mkdev("/dev/null",o.makedev(1,3)),Me.register(o.makedev(5,0),Me.default_tty_ops),Me.register(o.makedev(6,0),Me.default_tty1_ops),o.mkdev("/dev/tty",o.makedev(5,0)),o.mkdev("/dev/tty1",o.makedev(6,0));var e=new Uint8Array(1024),r=0,t=()=>(r===0&&(r=gr(e).byteLength),e[--r]);o.createDevice("/dev","random",t),o.createDevice("/dev","urandom",t),o.mkdir("/dev/shm"),o.mkdir("/dev/shm/tmp")},createSpecialDirectories(){o.mkdir("/proc");var e=o.mkdir("/proc/self");o.mkdir("/proc/self/fd"),o.mount({mount(){var r=o.createNode(e,"fd",16895,73);return r.node_ops={lookup(t,n){var a=+n,s=o.getStreamChecked(a),u={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>s.path}};return u.parent=u,u}},r}},{},"/proc/self/fd")},createStandardStreams(){i.stdin?o.createDevice("/dev","stdin",i.stdin):o.symlink("/dev/tty","/dev/stdin"),i.stdout?o.createDevice("/dev","stdout",null,i.stdout):o.symlink("/dev/tty","/dev/stdout"),i.stderr?o.createDevice("/dev","stderr",null,i.stderr):o.symlink("/dev/tty1","/dev/stderr");var e=o.open("/dev/stdin",0),r=o.open("/dev/stdout",1),t=o.open("/dev/stderr",1)},staticInit(){[44].forEach(e=>{o.genericErrors[e]=new o.ErrnoError(e),o.genericErrors[e].stack=""}),o.nameTable=new Array(4096),o.mount(D,{},"/"),o.createDefaultDirectories(),o.createDefaultDevices(),o.createSpecialDirectories(),o.filesystems={MEMFS:D}},init(e,r,t){o.init.initialized=!0,i.stdin=e||i.stdin,i.stdout=r||i.stdout,i.stderr=t||i.stderr,o.createStandardStreams()},quit(){o.init.initialized=!1;for(var e=0;ethis.length-1||y<0)){var O=y%this.chunkSize,te=y/this.chunkSize|0;return this.getter(te)[O]}}setDataGetter(y){this.getter=y}cacheLength(){var y=new XMLHttpRequest;if(y.open("HEAD",t,!1),y.send(null),!(y.status>=200&&y.status<300||y.status===304))throw new Error("Couldn't load "+t+". Status: "+y.status);var O=Number(y.getResponseHeader("Content-length")),te,de=(te=y.getResponseHeader("Accept-Ranges"))&&te==="bytes",ne=(te=y.getResponseHeader("Content-Encoding"))&&te==="gzip",fe=1024*1024;de||(fe=O);var ve=(ge,xe)=>{if(ge>xe)throw new Error("invalid range ("+ge+", "+xe+") or no bytes requested!");if(xe>O-1)throw new Error("only "+O+" bytes available! programmer error!");var ee=new XMLHttpRequest;if(ee.open("GET",t,!1),O!==fe&&ee.setRequestHeader("Range","bytes="+ge+"-"+xe),ee.responseType="arraybuffer",ee.overrideMimeType&&ee.overrideMimeType("text/plain; charset=x-user-defined"),ee.send(null),!(ee.status>=200&&ee.status<300||ee.status===304))throw new Error("Couldn't load "+t+". Status: "+ee.status);return ee.response!==void 0?new Uint8Array(ee.response||[]):or(ee.responseText||"",!0)},Ie=this;Ie.setDataGetter(ge=>{var xe=ge*fe,ee=(ge+1)*fe-1;if(ee=Math.min(ee,O-1),typeof Ie.chunks[ge]>"u"&&(Ie.chunks[ge]=ve(xe,ee)),typeof Ie.chunks[ge]>"u")throw new Error("doXHR failed!");return Ie.chunks[ge]}),(ne||!O)&&(fe=O=1,O=this.getter(0).length,fe=O,X("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=O,this._chunkSize=fe,this.lengthKnown=!0}get length(){return this.lengthKnown||this.cacheLength(),this._length}get chunkSize(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}if(typeof XMLHttpRequest<"u"){if(!h)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var u=new s,c={isDevice:!1,contents:u}}else var c={isDevice:!1,url:t};var v=o.createFile(e,r,c,n,a);c.contents?v.contents=c.contents:c.url&&(v.contents=null,v.url=c.url),Object.defineProperties(v,{usedBytes:{get:function(){return this.contents.length}}});var E={},G=Object.keys(v.stream_ops);G.forEach(B=>{var y=v.stream_ops[B];E[B]=(...O)=>(o.forceLoadFile(v),y(...O))});function x(B,y,O,te,de){var ne=B.node.contents;if(de>=ne.length)return 0;var fe=Math.min(ne.length-de,te);if(ne.slice)for(var ve=0;ve(o.forceLoadFile(v),x(B,y,O,te,de)),E.mmap=(B,y,O,te,de)=>{o.forceLoadFile(v);var ne=yr(y);if(!ne)throw new o.ErrnoError(48);return x(B,w,ne,y,O),{ptr:ne,allocated:!0}},v.stream_ops=E,v}},ue=(e,r)=>e?Fe(j,e,r):"",U={DEFAULT_POLLMASK:5,calculateAt(e,r,t){if(C.isAbs(r))return r;var n;if(e===-100)n=o.cwd();else{var a=U.getStreamFromFD(e);n=a.path}if(r.length==0){if(!t)throw new o.ErrnoError(44);return n}return C.join2(n,r)},doStat(e,r,t){var n=e(r);b[t>>2]=n.dev,b[t+4>>2]=n.mode,k[t+8>>2]=n.nlink,b[t+12>>2]=n.uid,b[t+16>>2]=n.gid,b[t+20>>2]=n.rdev,$=[n.size>>>0,(R=n.size,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+24>>2]=$[0],b[t+28>>2]=$[1],b[t+32>>2]=4096,b[t+36>>2]=n.blocks;var a=n.atime.getTime(),s=n.mtime.getTime(),u=n.ctime.getTime();return $=[Math.floor(a/1e3)>>>0,(R=Math.floor(a/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+40>>2]=$[0],b[t+44>>2]=$[1],k[t+48>>2]=a%1e3*1e3,$=[Math.floor(s/1e3)>>>0,(R=Math.floor(s/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+56>>2]=$[0],b[t+60>>2]=$[1],k[t+64>>2]=s%1e3*1e3,$=[Math.floor(u/1e3)>>>0,(R=Math.floor(u/1e3),+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+72>>2]=$[0],b[t+76>>2]=$[1],k[t+80>>2]=u%1e3*1e3,$=[n.ino>>>0,(R=n.ino,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[t+88>>2]=$[0],b[t+92>>2]=$[1],0},doMsync(e,r,t,n,a){if(!o.isFile(r.node.mode))throw new o.ErrnoError(43);if(n&2)return 0;var s=j.slice(e,e+t);o.msync(r,s,a,t,n)},getStreamFromFD(e){var r=o.getStreamChecked(e);return r},varargs:void 0,getStr(e){var r=ue(e);return r}};function lt(e,r,t,n){try{if(r=U.getStr(r),r=U.calculateAt(e,r),t&-8)return-28;var a=o.lookupPath(r,{follow:!0}),s=a.node;if(!s)return-44;var u="";return t&4&&(u+="r"),t&2&&(u+="w"),t&1&&(u+="x"),u&&o.nodePermissions(s,u)?-2:0}catch(c){if(typeof o>"u"||c.name!=="ErrnoError")throw c;return-c.errno}}function Se(){var e=b[+U.varargs>>2];return U.varargs+=4,e}var Oe=Se;function ct(e,r,t){U.varargs=t;try{var n=U.getStreamFromFD(e);switch(r){case 0:{var a=Se();if(a<0)return-28;for(;o.streams[a];)a++;var s;return s=o.dupStream(n,a),s.fd}case 1:case 2:return 0;case 3:return n.flags;case 4:{var a=Se();return n.flags|=a,0}case 12:{var a=Oe(),u=0;return W[a+u>>1]=2,0}case 13:case 14:return 0}return-28}catch(c){if(typeof o>"u"||c.name!=="ErrnoError")throw c;return-c.errno}}function dt(e,r){try{var t=U.getStreamFromFD(e);return U.doStat(o.stat,t.path,r)}catch(n){if(typeof o>"u"||n.name!=="ErrnoError")throw n;return-n.errno}}function ft(e,r,t){U.varargs=t;try{var n=U.getStreamFromFD(e);switch(r){case 21509:return n.tty?0:-59;case 21505:{if(!n.tty)return-59;if(n.tty.ops.ioctl_tcgets){var a=n.tty.ops.ioctl_tcgets(n),s=Oe();b[s>>2]=a.c_iflag||0,b[s+4>>2]=a.c_oflag||0,b[s+8>>2]=a.c_cflag||0,b[s+12>>2]=a.c_lflag||0;for(var u=0;u<32;u++)w[s+u+17]=a.c_cc[u]||0;return 0}return 0}case 21510:case 21511:case 21512:return n.tty?0:-59;case 21506:case 21507:case 21508:{if(!n.tty)return-59;if(n.tty.ops.ioctl_tcsets){for(var s=Oe(),c=b[s>>2],v=b[s+4>>2],E=b[s+8>>2],G=b[s+12>>2],x=[],u=0;u<32;u++)x.push(w[s+u+17]);return n.tty.ops.ioctl_tcsets(n.tty,r,{c_iflag:c,c_oflag:v,c_cflag:E,c_lflag:G,c_cc:x})}return 0}case 21519:{if(!n.tty)return-59;var s=Oe();return b[s>>2]=0,0}case 21520:return n.tty?-28:-59;case 21531:{var s=Oe();return o.ioctl(n,r,s)}case 21523:{if(!n.tty)return-59;if(n.tty.ops.ioctl_tiocgwinsz){var B=n.tty.ops.ioctl_tiocgwinsz(n.tty),s=Oe();W[s>>1]=B[0],W[s+2>>1]=B[1]}return 0}case 21524:return n.tty?0:-59;case 21515:return n.tty?0:-59;default:return-28}}catch(y){if(typeof o>"u"||y.name!=="ErrnoError")throw y;return-y.errno}}function vt(e,r,t,n){try{r=U.getStr(r);var a=n&256,s=n&4096;return n=n&-6401,r=U.calculateAt(e,r,s),U.doStat(a?o.lstat:o.stat,r,t)}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return-u.errno}}function mt(e,r,t,n){U.varargs=n;try{r=U.getStr(r),r=U.calculateAt(e,r);var a=n?Se():0;return o.open(r,t,a).fd}catch(s){if(typeof o>"u"||s.name!=="ErrnoError")throw s;return-s.errno}}var Ke=(e,r,t)=>tr(e,j,r,t);function _t(e,r,t,n){try{if(r=U.getStr(r),r=U.calculateAt(e,r),n<=0)return-28;var a=o.readlink(r),s=Math.min(n,rr(a)),u=w[t+s];return Ke(a,t,n+1),w[t+s]=u,s}catch(c){if(typeof o>"u"||c.name!=="ErrnoError")throw c;return-c.errno}}function gt(e){try{return e=U.getStr(e),o.rmdir(e),0}catch(r){if(typeof o>"u"||r.name!=="ErrnoError")throw r;return-r.errno}}function ht(e,r){try{return e=U.getStr(e),U.doStat(o.stat,e,r)}catch(t){if(typeof o>"u"||t.name!=="ErrnoError")throw t;return-t.errno}}function wt(e,r,t){try{return r=U.getStr(r),r=U.calculateAt(e,r),t===0?o.unlink(r):t===512?o.rmdir(r):_e("Invalid flags passed to unlinkat"),0}catch(n){if(typeof o>"u"||n.name!=="ErrnoError")throw n;return-n.errno}}var yt=()=>{_e("")},bt=1,Et=()=>bt,Mt=(e,r,t)=>j.copyWithin(e,r,r+t),ar=(e,r)=>r+2097152>>>0<4194305-!!e?(e>>>0)+r*4294967296:NaN;function Dt(e,r,t,n,a,s,u,c){var v=ar(a,s);try{if(isNaN(v))return 61;var E=U.getStreamFromFD(n),G=o.mmap(E,e,v,r,t),x=G.ptr;return b[u>>2]=G.allocated,k[c>>2]=x,0}catch(B){if(typeof o>"u"||B.name!=="ErrnoError")throw B;return-B.errno}}function Xt(e,r,t,n,a,s,u){var c=ar(s,u);try{var v=U.getStreamFromFD(a);t&2&&U.doMsync(e,v,r,n,c)}catch(E){if(typeof o>"u"||E.name!=="ErrnoError")throw E;return-E.errno}}var Lt=(e,r,t,n)=>{var a=new Date().getFullYear(),s=new Date(a,0,1),u=new Date(a,6,1),c=s.getTimezoneOffset(),v=u.getTimezoneOffset(),E=Math.max(c,v);k[e>>2]=E*60,b[r>>2]=+(c!=v);var G=y=>{var O=y>=0?"-":"+",te=Math.abs(y),de=String(Math.floor(te/60)).padStart(2,"0"),ne=String(te%60).padStart(2,"0");return`UTC${O}${de}${ne}`},x=G(c),B=G(v);v{ir.length=0;for(var t;t=j[e++];){var n=t!=105;n&=t!=112,r+=n&&r%8?4:0,ir.push(t==112?k[r>>2]:t==105?b[r>>2]:oe[r>>3]),r+=n?8:4}return ir},Rt=(e,r,t)=>{var n=Bt(r,t);return qr[e](...n)},kt=(e,r,t)=>Rt(e,r,t),Gt=()=>Date.now(),Ft;Ft=()=>performance.now();var Ot=()=>2147483648,xt=e=>{var r=S.buffer,t=(e-r.byteLength+65535)/65536;try{return S.grow(t),K(),1}catch{}},Ut=e=>{var r=j.length;e>>>=0;var t=Ot();if(e>t)return!1;for(var n=(v,E)=>v+(E-v%E)%E,a=1;a<=4;a*=2){var s=r*(1+.2/a);s=Math.min(s,e+100663296);var u=Math.min(t,n(Math.max(e,s),65536)),c=xt(u);if(c)return!0}return!1},sr={},Nt=()=>I||"./this.program",Ce=()=>{if(!Ce.strings){var e=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",r={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:e,_:Nt()};for(var t in sr)sr[t]===void 0?delete r[t]:r[t]=sr[t];var n=[];for(var t in r)n.push(`${t}=${r[t]}`);Ce.strings=n}return Ce.strings},Vt=(e,r)=>{for(var t=0;t{var t=0;return Ce().forEach((n,a)=>{var s=r+t;k[e+a*4>>2]=s,Vt(n,s),t+=n.length+1}),0},Ct=(e,r)=>{var t=Ce();k[e>>2]=t.length;var n=0;return t.forEach(a=>n+=a.length+1),k[r>>2]=n,0},It=0,Zt=()=>It>0,Yt=e=>{ae=e,Zt()||(P=!0),Q(e,new $r(e))},zt=(e,r)=>{ae=e,Yt(e)},jt=zt;function Pt(e){try{var r=U.getStreamFromFD(e);return o.close(r),0}catch(t){if(typeof o>"u"||t.name!=="ErrnoError")throw t;return t.errno}}var Jt=(e,r,t,n)=>{for(var a=0,s=0;s>2],c=k[r+4>>2];r+=8;var v=o.read(e,w,u,c,n);if(v<0)return-1;if(a+=v,v>2]=s,0}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return u.errno}}function Ht(e,r,t,n,a){var s=ar(r,t);try{if(isNaN(s))return 61;var u=U.getStreamFromFD(e);return o.llseek(u,s,n),$=[u.position>>>0,(R=u.position,+Math.abs(R)>=1?R>0?+Math.floor(R/4294967296)>>>0:~~+Math.ceil((R-+(~~R>>>0))/4294967296)>>>0:0)],b[a>>2]=$[0],b[a+4>>2]=$[1],u.getdents&&s===0&&n===0&&(u.getdents=null),0}catch(c){if(typeof o>"u"||c.name!=="ErrnoError")throw c;return c.errno}}var Tt=(e,r,t,n)=>{for(var a=0,s=0;s>2],c=k[r+4>>2];r+=8;var v=o.write(e,w,u,c,n);if(v<0)return-1;a+=v,typeof n<"u"&&(n+=v)}return a};function Qt(e,r,t,n){try{var a=U.getStreamFromFD(e),s=Tt(a,r,t);return k[n>>2]=s,0}catch(u){if(typeof o>"u"||u.name!=="ErrnoError")throw u;return u.errno}}o.createPreloadedFile=ut,o.staticInit();var qt={a:et,k:lt,h:ct,y:dt,z:ft,w:vt,d:mt,r:_t,s:gt,x:ht,t:wt,l:yt,i:Et,j:Mt,m:Dt,n:Xt,p:Lt,A:kt,b:Gt,q:Ut,u:Kt,v:Ct,e:jt,c:Pt,f:St,o:Ht,g:Qt},N=Qr(),$t=()=>($t=N.C)(),Wt=i._webidl_free=e=>(Wt=i._webidl_free=N.D)(e),At=i._free=e=>(At=i._free=N.E)(e),eo=i._webidl_malloc=e=>(eo=i._webidl_malloc=N.F)(e),ro=i._malloc=e=>(ro=i._malloc=N.G)(e),br=i._emscripten_bind_VoidPtr___destroy___0=e=>(br=i._emscripten_bind_VoidPtr___destroy___0=N.H)(e),Er=i._emscripten_bind_Graphviz_Graphviz_2=(e,r)=>(Er=i._emscripten_bind_Graphviz_Graphviz_2=N.I)(e,r),Mr=i._emscripten_bind_Graphviz_version_0=()=>(Mr=i._emscripten_bind_Graphviz_version_0=N.J)(),Dr=i._emscripten_bind_Graphviz_lastError_0=()=>(Dr=i._emscripten_bind_Graphviz_lastError_0=N.K)(),Xr=i._emscripten_bind_Graphviz_createFile_2=(e,r,t)=>(Xr=i._emscripten_bind_Graphviz_createFile_2=N.L)(e,r,t),Lr=i._emscripten_bind_Graphviz_layout_3=(e,r,t,n)=>(Lr=i._emscripten_bind_Graphviz_layout_3=N.M)(e,r,t,n),Br=i._emscripten_bind_Graphviz_acyclic_3=(e,r,t,n)=>(Br=i._emscripten_bind_Graphviz_acyclic_3=N.N)(e,r,t,n),Rr=i._emscripten_bind_Graphviz_tred_3=(e,r,t,n)=>(Rr=i._emscripten_bind_Graphviz_tred_3=N.O)(e,r,t,n),kr=i._emscripten_bind_Graphviz_unflatten_4=(e,r,t,n,a)=>(kr=i._emscripten_bind_Graphviz_unflatten_4=N.P)(e,r,t,n,a),Gr=i._emscripten_bind_Graphviz_get_layout_result_0=e=>(Gr=i._emscripten_bind_Graphviz_get_layout_result_0=N.Q)(e),Fr=i._emscripten_bind_Graphviz_set_layout_result_1=(e,r)=>(Fr=i._emscripten_bind_Graphviz_set_layout_result_1=N.R)(e,r),Or=i._emscripten_bind_Graphviz_get_acyclic_outFile_0=e=>(Or=i._emscripten_bind_Graphviz_get_acyclic_outFile_0=N.S)(e),xr=i._emscripten_bind_Graphviz_set_acyclic_outFile_1=(e,r)=>(xr=i._emscripten_bind_Graphviz_set_acyclic_outFile_1=N.T)(e,r),Ur=i._emscripten_bind_Graphviz_get_acyclic_num_rev_0=e=>(Ur=i._emscripten_bind_Graphviz_get_acyclic_num_rev_0=N.U)(e),Nr=i._emscripten_bind_Graphviz_set_acyclic_num_rev_1=(e,r)=>(Nr=i._emscripten_bind_Graphviz_set_acyclic_num_rev_1=N.V)(e,r),Vr=i._emscripten_bind_Graphviz_get_tred_out_0=e=>(Vr=i._emscripten_bind_Graphviz_get_tred_out_0=N.W)(e),Kr=i._emscripten_bind_Graphviz_set_tred_out_1=(e,r)=>(Kr=i._emscripten_bind_Graphviz_set_tred_out_1=N.X)(e,r),Cr=i._emscripten_bind_Graphviz_get_tred_err_0=e=>(Cr=i._emscripten_bind_Graphviz_get_tred_err_0=N.Y)(e),Ir=i._emscripten_bind_Graphviz_set_tred_err_1=(e,r)=>(Ir=i._emscripten_bind_Graphviz_set_tred_err_1=N.Z)(e,r),Zr=i._emscripten_bind_Graphviz___destroy___0=e=>(Zr=i._emscripten_bind_Graphviz___destroy___0=N._)(e),Yr=(e,r)=>(Yr=N.aa)(e,r),zr=e=>(zr=N.ba)(e);i.UTF8ToString=ue;var He;be=function e(){He||jr(),He||(be=e)};function jr(){if(ye>0||(Re(),ye>0))return;function e(){He||(He=!0,i.calledRun=!0,!P&&(Ne(),f(i),Pe()))}e()}jr();function pe(){}pe.prototype=Object.create(pe.prototype),pe.prototype.constructor=pe,pe.prototype.__class__=pe,pe.__cache__={},i.WrapperObject=pe;function Te(e){return(e||pe).__cache__}i.getCache=Te;function ur(e,r){var t=Te(r),n=t[e];return n||(n=Object.create((r||pe).prototype),n.ptr=e,t[e]=n)}i.wrapPointer=ur;function to(e,r){return ur(e.ptr,r)}i.castObject=to,i.NULL=ur(0);function oo(e){if(!e.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";e.__destroy__(),delete Te(e.__class__)[e.ptr]}i.destroy=oo;function no(e,r){return e.ptr===r.ptr}i.compare=no;function ao(e){return e.ptr}i.getPointer=ao;function io(e){return e.__class__}i.getClass=io;var F={buffer:0,size:0,pos:0,temps:[],needed:0,prepare(){if(F.needed){for(var e=0;e=F.size?(J(n>0),F.needed+=n,a=i._webidl_malloc(n),F.temps.push(a)):(a=F.buffer+F.pos,F.pos+=n),a},copy(e,r,t){t/=r.BYTES_PER_ELEMENT;for(var n=0;n?@[]^_`{|}~"';function xo(p){let l=p.length,d=[],i=0,f=0,m=-1;for(let _=0;_88?13:14;do d.push(i&255),i>>=8,f-=8;while(f>7);m=-1}}return m>-1&&d.push((i|m< - -`}}function Co(p){return p.map(Ko)}function Io(p,l){let d={images:[],files:[],...l};[...d.files,...Co(d.images)].forEach(i=>p.createFile(i.path,i.data))}var Zo=class Ue{constructor(l){this._module=l}static load(){return No().then(l=>new Ue(l))}static unload(){Vo()}version(){return this._module.Graphviz.prototype.version()}layout(l,d="svg",i="dot",f){if(!l)return"";let m=new this._module.Graphviz(f?.yInvert?1:0,f?.nop?f?.nop:0),_="",h="";try{Io(m,f);try{_=m.layout(l,d,i)}catch(M){h=M.message}h=m.lastError()||h}finally{this._module.destroy(m)}if(!_&&h)throw Ue.unload(),new Error(h);return _}acyclic(l,d=!1,i=!1){if(!l)return{acyclic:!1,num_rev:0,outFile:""};let f=new this._module.Graphviz,m=!1,_=0,h="",M="";try{try{m=f.acyclic(l,d,i),_=f.acyclic_num_rev,h=f.acyclic_outFile}catch(I){M=I.message}M=f.lastError()||M}finally{this._module.destroy(f)}if(M)throw Ue.unload(),new Error(M);return{acyclic:m,num_rev:_,outFile:h}}tred(l,d=!1,i=!1){if(!l)return{out:"",err:""};let f=new this._module.Graphviz,m="",_="",h="";try{try{f.tred(l,d,i),m=f.tred_out,_=f.tred_err}catch(M){h=M.message}h=f.lastError()||h}finally{this._module.destroy(f)}if(!m&&h)throw Ue.unload(),new Error(h);return{out:m,err:_}}unflatten(l,d=0,i=!1,f=0){if(!l)return"";let m=new this._module.Graphviz,_="",h="";try{try{_=m.unflatten(l,d,i,f)}catch(M){h=M.message}h=m.lastError()||h}finally{this._module.destroy(m)}if(!_&&h)throw Ue.unload(),new Error(h);return _}circo(l,d="svg",i){return this.layout(l,d,"circo",i)}dot(l,d="svg",i){return this.layout(l,d,"dot",i)}fdp(l,d="svg",i){return this.layout(l,d,"fdp",i)}sfdp(l,d="svg",i){return this.layout(l,d,"sfdp",i)}neato(l,d="svg",i){return this.layout(l,d,"neato",i)}osage(l,d="svg",i){return this.layout(l,d,"osage",i)}patchwork(l,d="svg",i){return this.layout(l,d,"patchwork",i)}twopi(l,d="svg",i){return this.layout(l,d,"twopi",i)}nop(l){return this.layout(l,"dot","nop")}nop2(l){return this.layout(l,"dot","nop2")}}; - -if(__exports != exports)module.exports = exports;return module.exports})); -//# sourceMappingURL=graphviz.umd.js.map diff --git a/graph/web/viewer.js b/graph/web/viewer.js index ed14651f..c36c369c 100644 --- a/graph/web/viewer.js +++ b/graph/web/viewer.js @@ -79,20 +79,13 @@ async function render(index) { const frameGeneration = generation; rendering = true; const frame = frames[index]; - busy = frame.snap && !frame.layout ? "Laying out frame " + (index + 1) + "..." : "Drawing..."; + busy = !frame.layout ? "Laying out frame " + (index + 1) + "..." : "Drawing..."; updateUI(); if (frame.pos >= 0) program.setSelectionRange(frame.pos, frame.pos + 1); try { - document.getElementById("elk").hidden = !frame.snap; - document.getElementById("dot").hidden = !!frame.snap; - if (frame.snap) { - if (!layout) layout = new GraphLayout(); - if (!frame.layout) frame.layout = await layout.run(frame.snap); - if (generation === frameGeneration) renderer.show(frame.snap, frame.layout, frame.evt); - } else { - document.getElementById("detail").hidden = true; - await drawDot(frame.dot); - } + if (!layout) layout = new GraphLayout(); + if (!frame.layout) frame.layout = await layout.run(frame.snap); + if (generation === frameGeneration) renderer.show(frame.snap, frame.layout, frame.evt); rendering = false; busy = ""; if (generation === frameGeneration) current = index; @@ -117,8 +110,8 @@ program.addEventListener("keydown", event => { }); document.getElementById("compile").addEventListener("click", get_program); document.getElementById("fit").addEventListener("click", () => { - if (frames[current]?.snap) { renderer.auto = true; renderer.fit(); } - else render(current); + renderer.auto = true; + renderer.fit(); }); document.getElementById("assocs").addEventListener("change", event => renderer.assocs(event.target.checked)); document.getElementById("near").addEventListener("change", () => renderer.mark()); @@ -143,22 +136,16 @@ try { done = true; compiling = false; updateUI(); - } else if (message.startsWith("{") || message.startsWith("digraph")) { + } else if (message.startsWith("{")) { let frame; try { - if (message.startsWith("{")) { - frame = JSON.parse(message); - if (frame.error) { - failure = "Compile error: " + frame.error; - updateUI(); - return; - } - if (frame.snap.ver !== 1) throw new Error("Unsupported graph version: " + frame.snap.ver); - } else { - // Earlier chapters still send bare DOT. - const pos = message.match(/\/\/ POS:\s*(\d+)/); - frame = {dot: message, pos: pos ? Number(pos[1]) : -1}; + frame = JSON.parse(message); + if (frame.error) { + failure = "Compile error: " + frame.error; + updateUI(); + return; } + if (frame.snap.ver !== 1) throw new Error("Unsupported graph version: " + frame.snap.ver); } catch (error) { reportError(error); return; @@ -181,9 +168,6 @@ try { updateUI(); }; - window.addEventListener("resize", () => { - if (current >= 0 && !frames[current].snap) render(current); - }); } catch (error) { reportError(error); } diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java index 19709b9e..e3ad665d 100644 --- a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java +++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java @@ -48,20 +48,8 @@ class AstVisitor extends TreeScanner { var methodSelect = node.getMethodSelect().toString(); var args = node.getArguments(); - if (methodSelect.endsWith(".parse")) { - current.showAfterParse = switch (args.size()) { - case 0 -> false; - case 1 -> (Boolean) literal(args.getFirst()); - default -> throw new RuntimeException("unexpected parse arguments" + node); - }; - } if (methodSelect.endsWith(".iterate")) { current.iterate = true; - current.showAfterIterate = switch (args.size()) { - case 0 -> false; - case 1 -> (Boolean) literal(args.getFirst()); - default -> throw new RuntimeException("unexpected parse arguments" + node); - }; } if (methodSelect.endsWith("assertEquals")) { if (inCatch) { @@ -141,9 +129,6 @@ class AstVisitor extends TreeScanner { default -> throw new RuntimeException("Parser constructor with unexpected arguments: " + node); } current.parserInput = (String) literal(args.get(0)); - } else if (node.getIdentifier().toString().equals("GraphVisualizer")) { - // only happens in Chapter02Tests - current.showAfterParse = true; } } return super.visitNewClass(node, unused); diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java index d0c6cfa8..ce652899 100644 --- a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java +++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java @@ -9,9 +9,7 @@ public class TestMethod { // if this is set you can ignore the rest public String parseErrorMessage; public boolean disablePeephole; - public boolean showAfterParse; public boolean iterate; - public boolean showAfterIterate; public boolean irPrinter; public boolean irPrinterLLVM; public String assertStopEquals; diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java index 115a075a..1f23bc07 100644 --- a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java +++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java @@ -109,16 +109,10 @@ public class RustPrinter { out.println("parser.nodes.disable_peephole = true;"); } out.println("let stop = parser.parse().unwrap();"); - if (test.showAfterParse) { - out.println("parser.show_graph();"); - } if (test.iterate) { out.println("parser.iterate(stop);"); out.println("parser.type_check(stop).unwrap();\n"); } - if (test.showAfterIterate) { - out.println("parser.show_graph();"); - } if (test.irPrinter) { out.println("println!(\"{}\", pretty_print" + (test.irPrinterLLVM ? "_llvm" : "") + "(&parser.nodes, stop, 99));"); } -- cgit v1.2.3