From 22df891893d30e9906d0b14fc3ae5d8e67d500b2 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 3 Oct 2026 14:03:48 -0700 Subject: Chapter renumber, and split ch 17 Renumber chapters 10a, 10b, 11, 12, 13, 14 -> 10, 11, 13, 12, [11,14]. Split ch 17 into a 17a and 17b; shuffle around im/mutable syntax to be consistent, and present it more slowly. THe new immutable syntax is pushed through all later chapters --- .github/workflows/linearize.sh | 6 + .gitignore | 13 +- README.md | 34 +- TypeAnalysis.md | 8 +- chapter02/README.md | 4 +- chapter03/README.md | 4 +- chapter05/README.md | 2 +- chapter09/README.md | 2 +- chapter10/Makefile | 7 + chapter10/README.md | 420 ++++++ chapter10/chapter10.iml | 28 + chapter10/docs/10-grammar.md | 125 ++ chapter10/docs/Makefile | 1 + chapter10/docs/example1.gv | 16 + chapter10/docs/example1.json | 23 + chapter10/docs/example1.svg | 118 ++ chapter10/docs/example2a.gv | 17 + chapter10/docs/example2a.json | 26 + chapter10/docs/example2a.svg | 130 ++ chapter10/docs/example2b.gv | 19 + chapter10/docs/example2b.json | 27 + chapter10/docs/example2b.svg | 135 ++ chapter10/docs/example2c.gv | 14 + chapter10/docs/example2c.json | 21 + chapter10/docs/example2c.svg | 109 ++ chapter10/docs/lattice.gv | 127 ++ chapter10/docs/lattice.svg | 593 ++++++++ chapter10/docs/load-after-store-after.json | 19 + chapter10/docs/load-after-store-after.svg | 97 ++ chapter10/docs/load-after-store-before.json | 20 + chapter10/docs/load-after-store-before.svg | 103 ++ chapter10/docs/type-control.svg | 5 + chapter10/docs/type-integer.svg | 5 + chapter10/docs/type-memory.svg | 5 + chapter10/docs/type-pointer.svg | 5 + chapter10/docs/type-struct.svg | 5 + chapter10/docs/type-tuple.svg | 5 + chapter10/pom.xml | 18 + .../main/java/com/seaofnodes/simple/IRPrinter.java | 31 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 208 +++ .../main/java/com/seaofnodes/simple/Parser.java | 827 +++++++++++ .../src/main/java/com/seaofnodes/simple/Utils.java | 65 + .../java/com/seaofnodes/simple/node/AddNode.java | 171 +++ .../java/com/seaofnodes/simple/node/BoolNode.java | 74 + .../java/com/seaofnodes/simple/node/CastNode.java | 36 + .../com/seaofnodes/simple/node/ConstantNode.java | 56 + .../java/com/seaofnodes/simple/node/DivNode.java | 40 + .../java/com/seaofnodes/simple/node/IfNode.java | 58 + .../java/com/seaofnodes/simple/node/LoadNode.java | 90 ++ .../java/com/seaofnodes/simple/node/LoopNode.java | 31 + .../java/com/seaofnodes/simple/node/MemOpNode.java | 46 + .../java/com/seaofnodes/simple/node/MinusNode.java | 30 + .../java/com/seaofnodes/simple/node/MulNode.java | 54 + .../java/com/seaofnodes/simple/node/MultiNode.java | 14 + .../java/com/seaofnodes/simple/node/NewNode.java | 36 + .../main/java/com/seaofnodes/simple/node/Node.java | 598 ++++++++ .../java/com/seaofnodes/simple/node/NotNode.java | 38 + .../java/com/seaofnodes/simple/node/PhiNode.java | 185 +++ .../java/com/seaofnodes/simple/node/ProjNode.java | 60 + .../com/seaofnodes/simple/node/RegionNode.java | 118 ++ .../com/seaofnodes/simple/node/ReturnNode.java | 43 + .../java/com/seaofnodes/simple/node/ScopeNode.java | 305 ++++ .../java/com/seaofnodes/simple/node/StartNode.java | 35 + .../java/com/seaofnodes/simple/node/StopNode.java | 75 + .../java/com/seaofnodes/simple/node/StoreNode.java | 67 + .../java/com/seaofnodes/simple/node/SubNode.java | 58 + .../simple/print/SimpleGraphAdapter.java | 40 + .../simple/print/SimpleGraphObserver.java | 45 + .../java/com/seaofnodes/simple/type/Field.java | 59 + .../main/java/com/seaofnodes/simple/type/Type.java | 187 +++ .../com/seaofnodes/simple/type/TypeInteger.java | 101 ++ .../java/com/seaofnodes/simple/type/TypeMem.java | 20 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 82 ++ .../com/seaofnodes/simple/type/TypeStruct.java | 127 ++ .../java/com/seaofnodes/simple/type/TypeTuple.java | 88 ++ .../java/com/seaofnodes/simple/Chapter01Test.java | 100 ++ .../java/com/seaofnodes/simple/Chapter02Test.java | 60 + .../java/com/seaofnodes/simple/Chapter03Test.java | 69 + .../java/com/seaofnodes/simple/Chapter04Test.java | 165 +++ .../java/com/seaofnodes/simple/Chapter05Test.java | 228 +++ .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ++++ .../java/com/seaofnodes/simple/Chapter07Test.java | 222 +++ .../java/com/seaofnodes/simple/Chapter08Test.java | 209 +++ .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ++++ .../java/com/seaofnodes/simple/Chapter10Test.java | 581 ++++++++ .../java/com/seaofnodes/simple/FuzzerWrap.java | 50 + .../java/com/seaofnodes/simple/SchedulerTest.java | 73 + .../test/java/com/seaofnodes/simple/TypeTest.java | 132 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 209 +++ .../com/seaofnodes/simple/evaluator/Scheduler.java | 592 ++++++++ .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 +++ .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 ++ .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 +++ .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 792 +++++++++++ chapter10a/Makefile | 7 - chapter10a/README.md | 424 ------ chapter10a/chapter10a.iml | 28 - chapter10a/docs/10-grammar.md | 125 -- chapter10a/docs/Makefile | 1 - chapter10a/docs/example1.gv | 16 - chapter10a/docs/example1.json | 23 - chapter10a/docs/example1.svg | 118 -- chapter10a/docs/example2a.gv | 17 - chapter10a/docs/example2a.json | 26 - chapter10a/docs/example2a.svg | 130 -- chapter10a/docs/example2b.gv | 19 - chapter10a/docs/example2b.json | 27 - chapter10a/docs/example2b.svg | 135 -- chapter10a/docs/example2c.gv | 14 - chapter10a/docs/example2c.json | 21 - chapter10a/docs/example2c.svg | 109 -- chapter10a/docs/lattice.gv | 127 -- chapter10a/docs/lattice.svg | 593 -------- chapter10a/docs/load-after-store-after.json | 19 - chapter10a/docs/load-after-store-after.svg | 97 -- chapter10a/docs/load-after-store-before.json | 20 - chapter10a/docs/load-after-store-before.svg | 103 -- chapter10a/docs/type-control.svg | 5 - chapter10a/docs/type-integer.svg | 5 - chapter10a/docs/type-memory.svg | 5 - chapter10a/docs/type-pointer.svg | 5 - chapter10a/docs/type-struct.svg | 5 - chapter10a/docs/type-tuple.svg | 5 - chapter10a/pom.xml | 18 - .../main/java/com/seaofnodes/simple/IRPrinter.java | 31 - .../main/java/com/seaofnodes/simple/IterPeeps.java | 208 --- .../main/java/com/seaofnodes/simple/Parser.java | 827 ----------- .../src/main/java/com/seaofnodes/simple/Utils.java | 65 - .../java/com/seaofnodes/simple/node/AddNode.java | 171 --- .../java/com/seaofnodes/simple/node/BoolNode.java | 74 - .../java/com/seaofnodes/simple/node/CastNode.java | 36 - .../com/seaofnodes/simple/node/ConstantNode.java | 56 - .../java/com/seaofnodes/simple/node/DivNode.java | 40 - .../java/com/seaofnodes/simple/node/IfNode.java | 58 - .../java/com/seaofnodes/simple/node/LoadNode.java | 90 -- .../java/com/seaofnodes/simple/node/LoopNode.java | 31 - .../java/com/seaofnodes/simple/node/MemOpNode.java | 46 - .../java/com/seaofnodes/simple/node/MinusNode.java | 30 - .../java/com/seaofnodes/simple/node/MulNode.java | 54 - .../java/com/seaofnodes/simple/node/MultiNode.java | 14 - .../java/com/seaofnodes/simple/node/NewNode.java | 36 - .../main/java/com/seaofnodes/simple/node/Node.java | 598 -------- .../java/com/seaofnodes/simple/node/NotNode.java | 38 - .../java/com/seaofnodes/simple/node/PhiNode.java | 185 --- .../java/com/seaofnodes/simple/node/ProjNode.java | 60 - .../com/seaofnodes/simple/node/RegionNode.java | 118 -- .../com/seaofnodes/simple/node/ReturnNode.java | 43 - .../java/com/seaofnodes/simple/node/ScopeNode.java | 305 ---- .../java/com/seaofnodes/simple/node/StartNode.java | 35 - .../java/com/seaofnodes/simple/node/StopNode.java | 75 - .../java/com/seaofnodes/simple/node/StoreNode.java | 67 - .../java/com/seaofnodes/simple/node/SubNode.java | 58 - .../simple/print/SimpleGraphAdapter.java | 40 - .../simple/print/SimpleGraphObserver.java | 45 - .../java/com/seaofnodes/simple/type/Field.java | 59 - .../main/java/com/seaofnodes/simple/type/Type.java | 187 --- .../com/seaofnodes/simple/type/TypeInteger.java | 101 -- .../java/com/seaofnodes/simple/type/TypeMem.java | 20 - .../com/seaofnodes/simple/type/TypeMemPtr.java | 82 -- .../com/seaofnodes/simple/type/TypeStruct.java | 127 -- .../java/com/seaofnodes/simple/type/TypeTuple.java | 88 -- .../java/com/seaofnodes/simple/Chapter01Test.java | 100 -- .../java/com/seaofnodes/simple/Chapter02Test.java | 60 - .../java/com/seaofnodes/simple/Chapter03Test.java | 69 - .../java/com/seaofnodes/simple/Chapter04Test.java | 165 --- .../java/com/seaofnodes/simple/Chapter05Test.java | 228 --- .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ---- .../java/com/seaofnodes/simple/Chapter07Test.java | 222 --- .../java/com/seaofnodes/simple/Chapter08Test.java | 209 --- .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ---- .../java/com/seaofnodes/simple/Chapter10Test.java | 581 -------- .../java/com/seaofnodes/simple/FuzzerWrap.java | 50 - .../java/com/seaofnodes/simple/SchedulerTest.java | 73 - .../test/java/com/seaofnodes/simple/TypeTest.java | 132 -- .../com/seaofnodes/simple/evaluator/Evaluator.java | 209 --- .../com/seaofnodes/simple/evaluator/Scheduler.java | 592 -------- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 --- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 -- .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 --- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 792 ----------- chapter10b/Makefile | 7 - chapter10b/README.md | 218 --- chapter10b/chapter10b.iml | 28 - chapter10b/docs/10-grammar.md | 125 -- chapter10b/docs/Makefile | 1 - chapter10b/docs/example1.gv | 20 - chapter10b/docs/example1.json | 31 - chapter10b/docs/example1.svg | 155 --- chapter10b/docs/example2a.gv | 17 - chapter10b/docs/example2a.json | 20 - chapter10b/docs/example2a.svg | 103 -- chapter10b/docs/example2b.gv | 19 - chapter10b/docs/example2b.json | 30 - chapter10b/docs/example2b.svg | 150 -- chapter10b/docs/example2c.gv | 15 - chapter10b/docs/example2c.json | 23 - chapter10b/docs/example2c.svg | 121 -- chapter10b/docs/lattice.gv | 133 -- chapter10b/docs/lattice.svg | 644 --------- chapter10b/pom.xml | 18 - .../main/java/com/seaofnodes/simple/IRPrinter.java | 32 - .../main/java/com/seaofnodes/simple/IterPeeps.java | 208 --- .../main/java/com/seaofnodes/simple/Parser.java | 843 ------------ .../src/main/java/com/seaofnodes/simple/Utils.java | 65 - .../java/com/seaofnodes/simple/node/AddNode.java | 171 --- .../java/com/seaofnodes/simple/node/BoolNode.java | 74 - .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 237 ---- .../java/com/seaofnodes/simple/node/CastNode.java | 36 - .../com/seaofnodes/simple/node/ConstantNode.java | 56 - .../java/com/seaofnodes/simple/node/DivNode.java | 40 - .../java/com/seaofnodes/simple/node/IfNode.java | 58 - .../java/com/seaofnodes/simple/node/LoadNode.java | 100 -- .../java/com/seaofnodes/simple/node/LoopNode.java | 31 - .../com/seaofnodes/simple/node/MemMergeNode.java | 61 - .../java/com/seaofnodes/simple/node/MemOpNode.java | 47 - .../com/seaofnodes/simple/node/MemPhiNode.java | 36 - .../java/com/seaofnodes/simple/node/MinusNode.java | 30 - .../java/com/seaofnodes/simple/node/MulNode.java | 54 - .../java/com/seaofnodes/simple/node/MultiNode.java | 14 - .../java/com/seaofnodes/simple/node/NewNode.java | 36 - .../main/java/com/seaofnodes/simple/node/Node.java | 604 -------- .../java/com/seaofnodes/simple/node/NotNode.java | 38 - .../java/com/seaofnodes/simple/node/PhiNode.java | 202 --- .../java/com/seaofnodes/simple/node/ProjNode.java | 60 - .../com/seaofnodes/simple/node/RegionNode.java | 118 -- .../com/seaofnodes/simple/node/ReturnNode.java | 43 - .../java/com/seaofnodes/simple/node/ScopeNode.java | 305 ---- .../java/com/seaofnodes/simple/node/StartNode.java | 35 - .../java/com/seaofnodes/simple/node/StopNode.java | 75 - .../java/com/seaofnodes/simple/node/StoreNode.java | 75 - .../java/com/seaofnodes/simple/node/SubNode.java | 58 - .../simple/print/SimpleGraphAdapter.java | 40 - .../simple/print/SimpleGraphObserver.java | 45 - .../java/com/seaofnodes/simple/type/Field.java | 59 - .../main/java/com/seaofnodes/simple/type/Type.java | 187 --- .../com/seaofnodes/simple/type/TypeInteger.java | 101 -- .../java/com/seaofnodes/simple/type/TypeMem.java | 66 - .../com/seaofnodes/simple/type/TypeMemPtr.java | 82 -- .../com/seaofnodes/simple/type/TypeStruct.java | 127 -- .../java/com/seaofnodes/simple/type/TypeTuple.java | 88 -- .../java/com/seaofnodes/simple/Chapter01Test.java | 100 -- .../java/com/seaofnodes/simple/Chapter02Test.java | 60 - .../java/com/seaofnodes/simple/Chapter03Test.java | 69 - .../java/com/seaofnodes/simple/Chapter04Test.java | 165 --- .../java/com/seaofnodes/simple/Chapter05Test.java | 228 --- .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ---- .../java/com/seaofnodes/simple/Chapter07Test.java | 222 --- .../java/com/seaofnodes/simple/Chapter08Test.java | 209 --- .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ---- .../java/com/seaofnodes/simple/Chapter10Test.java | 583 -------- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/FuzzerWrap.java | 50 - .../java/com/seaofnodes/simple/SchedulerTest.java | 73 - .../test/java/com/seaofnodes/simple/TypeTest.java | 145 -- .../com/seaofnodes/simple/evaluator/Evaluator.java | 210 --- .../com/seaofnodes/simple/evaluator/Scheduler.java | 599 -------- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 --- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 -- .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 --- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 792 ----------- chapter11/Makefile | 111 +- chapter11/README.md | 500 +++---- chapter11/chapter11.iml | 28 + chapter11/docs/10-grammar.md | 125 ++ chapter11/docs/11-grammar.md | 125 -- chapter11/docs/example1.gv | 20 + chapter11/docs/example1.json | 31 + chapter11/docs/example1.svg | 155 +++ chapter11/docs/example2a.gv | 17 + chapter11/docs/example2a.json | 20 + chapter11/docs/example2a.svg | 103 ++ chapter11/docs/example2b-final.json | 251 ++++ chapter11/docs/example2b-final.svg | 110 ++ chapter11/docs/example2b-merged.json | 339 +++++ chapter11/docs/example2b-merged.svg | 130 ++ chapter11/docs/example2b-split-y.json | 486 +++++++ chapter11/docs/example2b-split-y.svg | 157 +++ chapter11/docs/example2b.gv | 19 + chapter11/docs/example2b.json | 30 + chapter11/docs/example2b.svg | 150 ++ chapter11/docs/example2c.gv | 15 + chapter11/docs/example2c.json | 23 + chapter11/docs/example2c.svg | 121 ++ chapter11/docs/graph1.gv | 79 -- chapter11/docs/graph1.json | 25 - chapter11/docs/graph1.svg | 123 -- chapter11/docs/graph2.gv | 84 -- chapter11/docs/graph2.json | 25 - chapter11/docs/graph2.svg | 123 -- chapter11/docs/graph3-snip.jpg | Bin 19681 -> 0 bytes chapter11/docs/graph3-snip.json | 17 - chapter11/docs/graph3-snip.svg | 91 -- chapter11/docs/graph3.gv | 85 -- chapter11/docs/graph3.json | 26 - chapter11/docs/graph3.svg | 128 -- chapter11/docs/graph4.gv | 110 -- chapter11/docs/graph4.json | 24 - chapter11/docs/graph4.svg | 120 -- chapter11/docs/graph5.gv | 117 -- chapter11/docs/graph5.json | 24 - chapter11/docs/graph5.svg | 120 -- chapter11/docs/graph6.gv | 117 -- chapter11/docs/graph6.json | 25 - chapter11/docs/graph6.svg | 124 -- chapter11/docs/graph7.gv | 23 - chapter11/docs/graph7.json | 16 - chapter11/docs/graph7.svg | 78 -- chapter11/docs/graph8.gv | 45 - chapter11/docs/graph8.json | 21 - chapter11/docs/graph8.svg | 98 -- chapter11/docs/lattice.gv | 133 ++ chapter11/docs/lattice.svg | 644 +++++++++ .../com/seaofnodes/simple/GlobalCodeMotion.java | 272 ---- .../main/java/com/seaofnodes/simple/IRPrinter.java | 7 +- .../main/java/com/seaofnodes/simple/IterPeeps.java | 1 - .../main/java/com/seaofnodes/simple/Parser.java | 52 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 7 + .../java/com/seaofnodes/simple/node/CFGNode.java | 95 -- .../java/com/seaofnodes/simple/node/CProjNode.java | 62 - .../com/seaofnodes/simple/node/ConstantNode.java | 1 - .../java/com/seaofnodes/simple/node/IfNode.java | 18 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 4 +- .../java/com/seaofnodes/simple/node/LoopNode.java | 60 +- .../java/com/seaofnodes/simple/node/MemOpNode.java | 4 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 8 +- .../java/com/seaofnodes/simple/node/NeverNode.java | 20 - .../java/com/seaofnodes/simple/node/NewNode.java | 1 - .../main/java/com/seaofnodes/simple/node/Node.java | 61 +- .../java/com/seaofnodes/simple/node/OutNode.java | 7 - .../java/com/seaofnodes/simple/node/PhiNode.java | 9 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 18 +- .../com/seaofnodes/simple/node/RegionNode.java | 29 +- .../com/seaofnodes/simple/node/ReturnNode.java | 9 +- .../java/com/seaofnodes/simple/node/StartNode.java | 21 +- .../java/com/seaofnodes/simple/node/StopNode.java | 18 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 6 +- .../java/com/seaofnodes/simple/node/SubNode.java | 1 - .../java/com/seaofnodes/simple/node/XCtrlNode.java | 14 - .../simple/print/SimpleGraphAdapter.java | 8 +- .../java/com/seaofnodes/simple/type/Field.java | 3 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 3 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 14 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 48 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 24 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 33 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 613 +-------- .../java/com/seaofnodes/simple/FuzzerWrap.java | 2 +- .../com/seaofnodes/simple/evaluator/Evaluator.java | 3 +- .../com/seaofnodes/simple/evaluator/Scheduler.java | 14 +- chapter12/Makefile | 111 +- chapter12/README.md | 161 ++- chapter12/chapter12.iml | 28 + chapter12/docs/12-grammar.md | 10 +- chapter12/docs/lattice.gv | 154 --- chapter12/docs/lattice.svg | 732 ---------- .../com/seaofnodes/simple/GlobalCodeMotion.java | 271 ---- .../main/java/com/seaofnodes/simple/IRPrinter.java | 7 +- .../main/java/com/seaofnodes/simple/IterPeeps.java | 4 - .../main/java/com/seaofnodes/simple/Parser.java | 156 +-- .../src/main/java/com/seaofnodes/simple/Utils.java | 7 + .../java/com/seaofnodes/simple/node/AddFNode.java | 37 - .../java/com/seaofnodes/simple/node/AddNode.java | 7 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 36 +- .../java/com/seaofnodes/simple/node/CFGNode.java | 96 -- .../java/com/seaofnodes/simple/node/CProjNode.java | 62 - .../com/seaofnodes/simple/node/ConstantNode.java | 1 - .../java/com/seaofnodes/simple/node/DivFNode.java | 39 - .../java/com/seaofnodes/simple/node/DivNode.java | 1 - .../java/com/seaofnodes/simple/node/IfNode.java | 18 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 18 +- .../java/com/seaofnodes/simple/node/LoopNode.java | 63 +- .../java/com/seaofnodes/simple/node/MemOpNode.java | 7 +- .../com/seaofnodes/simple/node/MinusFNode.java | 30 - .../java/com/seaofnodes/simple/node/MinusNode.java | 1 - .../java/com/seaofnodes/simple/node/MulFNode.java | 44 - .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 16 +- .../java/com/seaofnodes/simple/node/NeverNode.java | 22 - .../java/com/seaofnodes/simple/node/NewNode.java | 1 - .../main/java/com/seaofnodes/simple/node/Node.java | 79 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/OutNode.java | 7 - .../java/com/seaofnodes/simple/node/PhiNode.java | 13 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 18 +- .../com/seaofnodes/simple/node/RegionNode.java | 29 +- .../com/seaofnodes/simple/node/ReturnNode.java | 9 +- .../java/com/seaofnodes/simple/node/StartNode.java | 21 +- .../java/com/seaofnodes/simple/node/StopNode.java | 18 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 25 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 37 - .../java/com/seaofnodes/simple/node/SubNode.java | 3 - .../com/seaofnodes/simple/node/ToFloatNode.java | 25 - .../java/com/seaofnodes/simple/node/XCtrlNode.java | 14 - .../simple/print/SimpleGraphAdapter.java | 8 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 11 +- .../java/com/seaofnodes/simple/type/TypeFloat.java | 101 -- .../java/com/seaofnodes/simple/type/TypeMem.java | 7 +- .../com/seaofnodes/simple/type/TypeMemPtr.java | 27 +- .../com/seaofnodes/simple/type/TypeStruct.java | 45 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 3 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 14 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 12 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 48 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 25 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 26 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 21 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 578 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 199 ++- .../java/com/seaofnodes/simple/FuzzerWrap.java | 2 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 4 +- .../com/seaofnodes/simple/evaluator/Evaluator.java | 27 +- .../com/seaofnodes/simple/evaluator/Scheduler.java | 14 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 84 +- chapter13/README.md | 474 ++++--- chapter13/chapter13.iml | 28 + chapter13/docs/13-grammar.md | 11 +- chapter13/docs/Makefile | 13 +- chapter13/docs/graph1.gv | 79 ++ chapter13/docs/graph1.json | 25 + chapter13/docs/graph1.svg | 123 ++ chapter13/docs/graph2.gv | 84 ++ chapter13/docs/graph2.json | 25 + chapter13/docs/graph2.svg | 123 ++ chapter13/docs/graph3-snip.jpg | Bin 0 -> 19681 bytes chapter13/docs/graph3-snip.json | 17 + chapter13/docs/graph3-snip.svg | 91 ++ chapter13/docs/graph3.gv | 85 ++ chapter13/docs/graph3.json | 26 + chapter13/docs/graph3.svg | 128 ++ chapter13/docs/graph4.gv | 110 ++ chapter13/docs/graph4.json | 24 + chapter13/docs/graph4.svg | 120 ++ chapter13/docs/graph5.gv | 117 ++ chapter13/docs/graph5.json | 24 + chapter13/docs/graph5.svg | 120 ++ chapter13/docs/graph6.gv | 117 ++ chapter13/docs/graph6.json | 25 + chapter13/docs/graph6.svg | 124 ++ chapter13/docs/graph7.gv | 23 + chapter13/docs/graph7.json | 16 + chapter13/docs/graph7.svg | 78 ++ chapter13/docs/graph8.gv | 45 + chapter13/docs/graph8.json | 21 + chapter13/docs/graph8.svg | 98 ++ .../main/java/com/seaofnodes/simple/Parser.java | 48 +- .../java/com/seaofnodes/simple/node/AddFNode.java | 37 - .../java/com/seaofnodes/simple/node/AddNode.java | 1 - .../java/com/seaofnodes/simple/node/BoolNode.java | 36 +- .../java/com/seaofnodes/simple/node/DivFNode.java | 39 - .../java/com/seaofnodes/simple/node/DivNode.java | 1 - .../com/seaofnodes/simple/node/MinusFNode.java | 30 - .../java/com/seaofnodes/simple/node/MinusNode.java | 1 - .../java/com/seaofnodes/simple/node/MulFNode.java | 44 - .../java/com/seaofnodes/simple/node/MulNode.java | 1 - .../main/java/com/seaofnodes/simple/node/Node.java | 21 - .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 37 - .../java/com/seaofnodes/simple/node/SubNode.java | 1 - .../com/seaofnodes/simple/node/ToFloatNode.java | 25 - .../simple/print/SimpleGraphAdapter.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 29 +- .../java/com/seaofnodes/simple/type/TypeFloat.java | 101 -- .../com/seaofnodes/simple/type/TypeStruct.java | 16 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 578 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 196 ++- .../java/com/seaofnodes/simple/Chapter13Test.java | 553 ++++++-- .../com/seaofnodes/simple/evaluator/Evaluator.java | 24 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 26 +- chapter14/README.md | 106 +- chapter14/chapter14.iml | 28 + .../com/seaofnodes/simple/type/TypeStruct.java | 14 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 578 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 219 ++- .../java/com/seaofnodes/simple/Chapter13Test.java | 553 ++++++-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 95 ++ chapter15/README.md | 3 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 565 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 194 ++- .../java/com/seaofnodes/simple/Chapter13Test.java | 542 ++++++-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ chapter16/README.md | 54 +- .../main/java/com/seaofnodes/simple/Parser.java | 26 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 600 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 195 ++- .../java/com/seaofnodes/simple/Chapter13Test.java | 564 ++++++-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter16Test.java | 58 +- chapter17/Makefile | 106 -- chapter17/README.md | 186 --- chapter17/docs/17-grammar.md | 130 -- chapter17/docs/Makefile | 12 - chapter17/pom.xml | 18 - .../src/main/java/com/seaofnodes/simple/Ary.java | 217 --- .../com/seaofnodes/simple/GlobalCodeMotion.java | 238 ---- .../main/java/com/seaofnodes/simple/IRPrinter.java | 33 - .../main/java/com/seaofnodes/simple/IterPeeps.java | 211 --- .../main/java/com/seaofnodes/simple/Parser.java | 1427 ------------------- .../src/main/java/com/seaofnodes/simple/Utils.java | 53 - .../java/com/seaofnodes/simple/node/AddFNode.java | 37 - .../java/com/seaofnodes/simple/node/AddNode.java | 186 --- .../java/com/seaofnodes/simple/node/AndNode.java | 61 - .../java/com/seaofnodes/simple/node/BoolNode.java | 114 -- .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 237 ---- .../java/com/seaofnodes/simple/node/CFGNode.java | 136 -- .../java/com/seaofnodes/simple/node/CProjNode.java | 61 - .../java/com/seaofnodes/simple/node/CastNode.java | 37 - .../com/seaofnodes/simple/node/ConstantNode.java | 56 - .../java/com/seaofnodes/simple/node/DivFNode.java | 39 - .../java/com/seaofnodes/simple/node/DivNode.java | 40 - .../java/com/seaofnodes/simple/node/IfNode.java | 62 - .../java/com/seaofnodes/simple/node/LoadNode.java | 221 --- .../java/com/seaofnodes/simple/node/LoopNode.java | 61 - .../com/seaofnodes/simple/node/MemMergeNode.java | 90 -- .../java/com/seaofnodes/simple/node/MemOpNode.java | 62 - .../com/seaofnodes/simple/node/MemPhiNode.java | 42 - .../com/seaofnodes/simple/node/MinusFNode.java | 30 - .../java/com/seaofnodes/simple/node/MinusNode.java | 36 - .../java/com/seaofnodes/simple/node/MulFNode.java | 44 - .../java/com/seaofnodes/simple/node/MulNode.java | 61 - .../java/com/seaofnodes/simple/node/MultiNode.java | 20 - .../java/com/seaofnodes/simple/node/NeverNode.java | 22 - .../java/com/seaofnodes/simple/node/NewNode.java | 60 - .../main/java/com/seaofnodes/simple/node/Node.java | 611 --------- .../java/com/seaofnodes/simple/node/NotNode.java | 39 - .../java/com/seaofnodes/simple/node/OrNode.java | 58 - .../java/com/seaofnodes/simple/node/OutNode.java | 7 - .../java/com/seaofnodes/simple/node/PhiNode.java | 201 --- .../java/com/seaofnodes/simple/node/ProjNode.java | 47 - .../com/seaofnodes/simple/node/ReadOnlyNode.java | 26 - .../com/seaofnodes/simple/node/RegionNode.java | 119 -- .../com/seaofnodes/simple/node/ReturnNode.java | 42 - .../com/seaofnodes/simple/node/RoundF32Node.java | 33 - .../java/com/seaofnodes/simple/node/SarNode.java | 54 - .../java/com/seaofnodes/simple/node/ScopeNode.java | 407 ------ .../java/com/seaofnodes/simple/node/ShlNode.java | 57 - .../java/com/seaofnodes/simple/node/ShrNode.java | 50 - .../java/com/seaofnodes/simple/node/StartNode.java | 36 - .../java/com/seaofnodes/simple/node/StopNode.java | 89 -- .../java/com/seaofnodes/simple/node/StoreNode.java | 125 -- .../com/seaofnodes/simple/node/StructNode.java | 45 - .../java/com/seaofnodes/simple/node/SubFNode.java | 37 - .../java/com/seaofnodes/simple/node/SubNode.java | 64 - .../com/seaofnodes/simple/node/ToFloatNode.java | 25 - .../java/com/seaofnodes/simple/node/XCtrlNode.java | 14 - .../java/com/seaofnodes/simple/node/XorNode.java | 58 - .../simple/print/SimpleGraphAdapter.java | 42 - .../simple/print/SimpleGraphObserver.java | 45 - .../java/com/seaofnodes/simple/type/Field.java | 77 -- .../main/java/com/seaofnodes/simple/type/Type.java | 218 --- .../java/com/seaofnodes/simple/type/TypeFloat.java | 120 -- .../com/seaofnodes/simple/type/TypeInteger.java | 133 -- .../java/com/seaofnodes/simple/type/TypeMem.java | 69 - .../com/seaofnodes/simple/type/TypeMemPtr.java | 102 -- .../com/seaofnodes/simple/type/TypeStruct.java | 262 ---- .../java/com/seaofnodes/simple/type/TypeTuple.java | 95 -- .../java/com/seaofnodes/simple/BrainFuckTest.java | 72 - .../java/com/seaofnodes/simple/Chapter01Test.java | 100 -- .../java/com/seaofnodes/simple/Chapter02Test.java | 60 - .../java/com/seaofnodes/simple/Chapter03Test.java | 69 - .../java/com/seaofnodes/simple/Chapter04Test.java | 165 --- .../java/com/seaofnodes/simple/Chapter05Test.java | 227 --- .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ---- .../java/com/seaofnodes/simple/Chapter07Test.java | 221 --- .../java/com/seaofnodes/simple/Chapter08Test.java | 208 --- .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ---- .../java/com/seaofnodes/simple/Chapter10Test.java | 572 -------- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 - .../java/com/seaofnodes/simple/Chapter11Test.java | 554 -------- .../java/com/seaofnodes/simple/Chapter12Test.java | 83 -- .../java/com/seaofnodes/simple/Chapter13Test.java | 196 --- .../java/com/seaofnodes/simple/Chapter14Test.java | 227 --- .../java/com/seaofnodes/simple/Chapter15Test.java | 387 ------ .../java/com/seaofnodes/simple/Chapter16Test.java | 280 ---- .../java/com/seaofnodes/simple/Chapter17Test.java | 500 ------- .../seaofnodes/simple/CompoundAssignmentTest.java | 77 -- .../java/com/seaofnodes/simple/FuzzerWrap.java | 58 - .../java/com/seaofnodes/simple/SchedulerTest.java | 73 - .../test/java/com/seaofnodes/simple/TypeTest.java | 157 --- .../com/seaofnodes/simple/evaluator/Evaluator.java | 374 ----- .../com/seaofnodes/simple/evaluator/Scheduler.java | 596 -------- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 --- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 -- .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 --- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 880 ------------ chapter17a/Makefile | 106 ++ chapter17a/README.md | 135 ++ chapter17a/chapter17a.iml | 28 + chapter17a/docs/17-grammar.md | 17 + chapter17a/docs/Makefile | 12 + chapter17a/pom.xml | 18 + .../src/main/java/com/seaofnodes/simple/Ary.java | 217 +++ .../com/seaofnodes/simple/GlobalCodeMotion.java | 238 ++++ .../main/java/com/seaofnodes/simple/IRPrinter.java | 33 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 211 +++ .../main/java/com/seaofnodes/simple/Parser.java | 1282 +++++++++++++++++ .../src/main/java/com/seaofnodes/simple/Utils.java | 53 + .../java/com/seaofnodes/simple/node/AddFNode.java | 37 + .../java/com/seaofnodes/simple/node/AddNode.java | 186 +++ .../java/com/seaofnodes/simple/node/AndNode.java | 61 + .../java/com/seaofnodes/simple/node/BoolNode.java | 114 ++ .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 237 ++++ .../java/com/seaofnodes/simple/node/CFGNode.java | 136 ++ .../java/com/seaofnodes/simple/node/CProjNode.java | 61 + .../java/com/seaofnodes/simple/node/CastNode.java | 37 + .../com/seaofnodes/simple/node/ConstantNode.java | 56 + .../java/com/seaofnodes/simple/node/DivFNode.java | 39 + .../java/com/seaofnodes/simple/node/DivNode.java | 40 + .../java/com/seaofnodes/simple/node/IfNode.java | 62 + .../java/com/seaofnodes/simple/node/LoadNode.java | 222 +++ .../java/com/seaofnodes/simple/node/LoopNode.java | 61 + .../com/seaofnodes/simple/node/MemMergeNode.java | 90 ++ .../java/com/seaofnodes/simple/node/MemOpNode.java | 62 + .../com/seaofnodes/simple/node/MemPhiNode.java | 42 + .../com/seaofnodes/simple/node/MinusFNode.java | 30 + .../java/com/seaofnodes/simple/node/MinusNode.java | 36 + .../java/com/seaofnodes/simple/node/MulFNode.java | 44 + .../java/com/seaofnodes/simple/node/MulNode.java | 61 + .../java/com/seaofnodes/simple/node/MultiNode.java | 20 + .../java/com/seaofnodes/simple/node/NeverNode.java | 22 + .../java/com/seaofnodes/simple/node/NewNode.java | 60 + .../main/java/com/seaofnodes/simple/node/Node.java | 610 ++++++++ .../java/com/seaofnodes/simple/node/NotNode.java | 39 + .../java/com/seaofnodes/simple/node/OrNode.java | 58 + .../java/com/seaofnodes/simple/node/OutNode.java | 7 + .../java/com/seaofnodes/simple/node/PhiNode.java | 201 +++ .../java/com/seaofnodes/simple/node/ProjNode.java | 47 + .../com/seaofnodes/simple/node/ReadOnlyNode.java | 26 + .../com/seaofnodes/simple/node/RegionNode.java | 119 ++ .../com/seaofnodes/simple/node/ReturnNode.java | 42 + .../com/seaofnodes/simple/node/RoundF32Node.java | 33 + .../java/com/seaofnodes/simple/node/SarNode.java | 54 + .../java/com/seaofnodes/simple/node/ScopeNode.java | 416 ++++++ .../java/com/seaofnodes/simple/node/ShlNode.java | 57 + .../java/com/seaofnodes/simple/node/ShrNode.java | 50 + .../java/com/seaofnodes/simple/node/StartNode.java | 36 + .../java/com/seaofnodes/simple/node/StopNode.java | 89 ++ .../java/com/seaofnodes/simple/node/StoreNode.java | 125 ++ .../com/seaofnodes/simple/node/StructNode.java | 45 + .../java/com/seaofnodes/simple/node/SubFNode.java | 37 + .../java/com/seaofnodes/simple/node/SubNode.java | 64 + .../com/seaofnodes/simple/node/ToFloatNode.java | 25 + .../java/com/seaofnodes/simple/node/XCtrlNode.java | 14 + .../java/com/seaofnodes/simple/node/XorNode.java | 58 + .../simple/print/SimpleGraphAdapter.java | 42 + .../simple/print/SimpleGraphObserver.java | 45 + .../java/com/seaofnodes/simple/type/Field.java | 77 ++ .../main/java/com/seaofnodes/simple/type/Type.java | 222 +++ .../java/com/seaofnodes/simple/type/TypeFloat.java | 120 ++ .../com/seaofnodes/simple/type/TypeInteger.java | 133 ++ .../java/com/seaofnodes/simple/type/TypeMem.java | 69 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 123 ++ .../com/seaofnodes/simple/type/TypeStruct.java | 262 ++++ .../java/com/seaofnodes/simple/type/TypeTuple.java | 95 ++ .../java/com/seaofnodes/simple/Chapter01Test.java | 100 ++ .../java/com/seaofnodes/simple/Chapter02Test.java | 60 + .../java/com/seaofnodes/simple/Chapter03Test.java | 69 + .../java/com/seaofnodes/simple/Chapter04Test.java | 165 +++ .../java/com/seaofnodes/simple/Chapter05Test.java | 227 +++ .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ++++ .../java/com/seaofnodes/simple/Chapter07Test.java | 221 +++ .../java/com/seaofnodes/simple/Chapter08Test.java | 208 +++ .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ++++ .../java/com/seaofnodes/simple/Chapter10Test.java | 572 ++++++++ .../java/com/seaofnodes/simple/Chapter11Test.java | 64 + .../java/com/seaofnodes/simple/Chapter12Test.java | 196 +++ .../java/com/seaofnodes/simple/Chapter13Test.java | 554 ++++++++ .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 227 +++ .../java/com/seaofnodes/simple/Chapter15Test.java | 387 ++++++ .../java/com/seaofnodes/simple/Chapter16Test.java | 171 +++ .../java/com/seaofnodes/simple/Chapter17aTest.java | 107 ++ .../java/com/seaofnodes/simple/FuzzerWrap.java | 58 + .../java/com/seaofnodes/simple/MutabilityTest.java | 57 + .../java/com/seaofnodes/simple/SchedulerTest.java | 73 + .../test/java/com/seaofnodes/simple/TypeTest.java | 157 +++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 374 +++++ .../com/seaofnodes/simple/evaluator/Scheduler.java | 596 ++++++++ .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 +++ .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 ++ .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 +++ .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 882 ++++++++++++ chapter17b/Makefile | 106 ++ chapter17b/README.md | 112 ++ chapter17b/chapter17b.iml | 28 + chapter17b/docs/17-grammar.md | 22 + chapter17b/docs/Makefile | 12 + chapter17b/pom.xml | 18 + .../src/main/java/com/seaofnodes/simple/Ary.java | 217 +++ .../com/seaofnodes/simple/GlobalCodeMotion.java | 238 ++++ .../main/java/com/seaofnodes/simple/IRPrinter.java | 33 + .../main/java/com/seaofnodes/simple/IterPeeps.java | 211 +++ .../main/java/com/seaofnodes/simple/Parser.java | 1450 ++++++++++++++++++++ .../src/main/java/com/seaofnodes/simple/Utils.java | 53 + .../java/com/seaofnodes/simple/node/AddFNode.java | 37 + .../java/com/seaofnodes/simple/node/AddNode.java | 186 +++ .../java/com/seaofnodes/simple/node/AndNode.java | 61 + .../java/com/seaofnodes/simple/node/BoolNode.java | 114 ++ .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 237 ++++ .../java/com/seaofnodes/simple/node/CFGNode.java | 136 ++ .../java/com/seaofnodes/simple/node/CProjNode.java | 61 + .../java/com/seaofnodes/simple/node/CastNode.java | 37 + .../com/seaofnodes/simple/node/ConstantNode.java | 56 + .../java/com/seaofnodes/simple/node/DivFNode.java | 39 + .../java/com/seaofnodes/simple/node/DivNode.java | 40 + .../java/com/seaofnodes/simple/node/IfNode.java | 62 + .../java/com/seaofnodes/simple/node/LoadNode.java | 222 +++ .../java/com/seaofnodes/simple/node/LoopNode.java | 61 + .../com/seaofnodes/simple/node/MemMergeNode.java | 90 ++ .../java/com/seaofnodes/simple/node/MemOpNode.java | 62 + .../com/seaofnodes/simple/node/MemPhiNode.java | 42 + .../com/seaofnodes/simple/node/MinusFNode.java | 30 + .../java/com/seaofnodes/simple/node/MinusNode.java | 36 + .../java/com/seaofnodes/simple/node/MulFNode.java | 44 + .../java/com/seaofnodes/simple/node/MulNode.java | 61 + .../java/com/seaofnodes/simple/node/MultiNode.java | 20 + .../java/com/seaofnodes/simple/node/NeverNode.java | 22 + .../java/com/seaofnodes/simple/node/NewNode.java | 60 + .../main/java/com/seaofnodes/simple/node/Node.java | 610 ++++++++ .../java/com/seaofnodes/simple/node/NotNode.java | 39 + .../java/com/seaofnodes/simple/node/OrNode.java | 58 + .../java/com/seaofnodes/simple/node/OutNode.java | 7 + .../java/com/seaofnodes/simple/node/PhiNode.java | 201 +++ .../java/com/seaofnodes/simple/node/ProjNode.java | 47 + .../com/seaofnodes/simple/node/ReadOnlyNode.java | 26 + .../com/seaofnodes/simple/node/RegionNode.java | 119 ++ .../com/seaofnodes/simple/node/ReturnNode.java | 42 + .../com/seaofnodes/simple/node/RoundF32Node.java | 33 + .../java/com/seaofnodes/simple/node/SarNode.java | 54 + .../java/com/seaofnodes/simple/node/ScopeNode.java | 416 ++++++ .../java/com/seaofnodes/simple/node/ShlNode.java | 57 + .../java/com/seaofnodes/simple/node/ShrNode.java | 50 + .../java/com/seaofnodes/simple/node/StartNode.java | 36 + .../java/com/seaofnodes/simple/node/StopNode.java | 89 ++ .../java/com/seaofnodes/simple/node/StoreNode.java | 125 ++ .../com/seaofnodes/simple/node/StructNode.java | 45 + .../java/com/seaofnodes/simple/node/SubFNode.java | 37 + .../java/com/seaofnodes/simple/node/SubNode.java | 64 + .../com/seaofnodes/simple/node/ToFloatNode.java | 25 + .../java/com/seaofnodes/simple/node/XCtrlNode.java | 14 + .../java/com/seaofnodes/simple/node/XorNode.java | 58 + .../simple/print/SimpleGraphAdapter.java | 42 + .../simple/print/SimpleGraphObserver.java | 45 + .../java/com/seaofnodes/simple/type/Field.java | 77 ++ .../main/java/com/seaofnodes/simple/type/Type.java | 222 +++ .../java/com/seaofnodes/simple/type/TypeFloat.java | 120 ++ .../com/seaofnodes/simple/type/TypeInteger.java | 133 ++ .../java/com/seaofnodes/simple/type/TypeMem.java | 69 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 123 ++ .../com/seaofnodes/simple/type/TypeStruct.java | 262 ++++ .../java/com/seaofnodes/simple/type/TypeTuple.java | 95 ++ .../java/com/seaofnodes/simple/BrainFuckTest.java | 72 + .../java/com/seaofnodes/simple/Chapter01Test.java | 100 ++ .../java/com/seaofnodes/simple/Chapter02Test.java | 60 + .../java/com/seaofnodes/simple/Chapter03Test.java | 69 + .../java/com/seaofnodes/simple/Chapter04Test.java | 165 +++ .../java/com/seaofnodes/simple/Chapter05Test.java | 227 +++ .../java/com/seaofnodes/simple/Chapter06Test.java | 294 ++++ .../java/com/seaofnodes/simple/Chapter07Test.java | 221 +++ .../java/com/seaofnodes/simple/Chapter08Test.java | 208 +++ .../java/com/seaofnodes/simple/Chapter09Test.java | 301 ++++ .../java/com/seaofnodes/simple/Chapter10Test.java | 572 ++++++++ .../java/com/seaofnodes/simple/Chapter11Test.java | 64 + .../java/com/seaofnodes/simple/Chapter12Test.java | 196 +++ .../java/com/seaofnodes/simple/Chapter13Test.java | 554 ++++++++ .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 227 +++ .../java/com/seaofnodes/simple/Chapter15Test.java | 387 ++++++ .../java/com/seaofnodes/simple/Chapter16Test.java | 171 +++ .../java/com/seaofnodes/simple/Chapter17aTest.java | 107 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 500 +++++++ .../seaofnodes/simple/CompoundAssignmentTest.java | 77 ++ .../java/com/seaofnodes/simple/FuzzerWrap.java | 58 + .../java/com/seaofnodes/simple/MutabilityTest.java | 63 + .../java/com/seaofnodes/simple/SchedulerTest.java | 73 + .../test/java/com/seaofnodes/simple/TypeTest.java | 157 +++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 374 +++++ .../com/seaofnodes/simple/evaluator/Scheduler.java | 596 ++++++++ .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 199 +++ .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 142 ++ .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 176 +++ .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 882 ++++++++++++ chapter18/#README.md# | 274 ++++ chapter18/README.md | 23 +- .../main/java/com/seaofnodes/simple/Parser.java | 106 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 11 +- .../main/java/com/seaofnodes/simple/node/Node.java | 5 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 45 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 52 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 65 - .../java/com/seaofnodes/simple/Chapter11Test.java | 602 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 263 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 623 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 556 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 107 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 556 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 26 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 8 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 72 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../main/java/com/seaofnodes/simple/Parser.java | 106 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 7 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 45 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 52 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 59 - .../java/com/seaofnodes/simple/Chapter11Test.java | 596 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 263 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 623 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 556 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 107 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 556 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 16 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 8 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 72 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../test/java/com/seaofnodes/simple/progs/fcn.smp | 4 +- .../test/java/com/seaofnodes/simple/progs/fcn3.smp | 6 +- .../main/java/com/seaofnodes/simple/Parser.java | 106 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 7 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 45 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 52 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 - .../java/com/seaofnodes/simple/Chapter11Test.java | 597 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 261 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 624 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 82 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 555 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 108 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 555 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 16 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 8 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 73 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../test/java/com/seaofnodes/simple/progs/fcn.smp | 4 +- .../test/java/com/seaofnodes/simple/progs/fcn3.smp | 6 +- chapter21/README.md | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 106 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 7 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 45 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 52 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 - .../java/com/seaofnodes/simple/Chapter11Test.java | 597 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 261 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 624 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 82 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 555 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 108 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 555 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 28 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 8 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../com/seaofnodes/simple/MemoryEncodingTest.java | 12 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 73 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../java/com/seaofnodes/simple/progs/antiDep1.smp | 2 +- .../java/com/seaofnodes/simple/progs/person.smp | 2 +- .../com/seaofnodes/simple/progs/stringHash.smp | 6 +- chapter22/README.md | 2 +- chapter22/docs/examples/BubbleSort.smp | 10 +- .../main/java/com/seaofnodes/simple/Parser.java | 107 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 7 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 45 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 52 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 - .../java/com/seaofnodes/simple/Chapter11Test.java | 597 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 262 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 625 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 82 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 555 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 108 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 555 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 20 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../com/seaofnodes/simple/MemoryEncodingTest.java | 12 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 73 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../java/com/seaofnodes/simple/progs/antiDep1.smp | 2 +- .../java/com/seaofnodes/simple/progs/person.smp | 2 +- .../java/com/seaofnodes/simple/progs/person21.smp | 2 +- .../com/seaofnodes/simple/progs/riscv_load.smp | 2 +- .../com/seaofnodes/simple/progs/sext_str_1.smp | 2 +- .../seaofnodes/simple/progs/sext_str_fold_away.smp | 2 +- .../simple/progs/sext_str_not_fold_away.smp | 2 +- .../simple/progs/sext_str_not_fold_away_2.smp | 2 +- .../com/seaofnodes/simple/progs/stringHash.smp | 6 +- chapter23/README.md | 12 +- chapter23/docs/examples/BubbleSort.smp | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 130 +- .../src/main/java/com/seaofnodes/simple/Var.java | 1 + .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/CastNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 4 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 4 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 9 + .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 61 +- .../com/seaofnodes/simple/type/TypeStruct.java | 5 +- chapter23/src/main/smp/sys.smp | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 56 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 - .../java/com/seaofnodes/simple/Chapter11Test.java | 597 +------- .../java/com/seaofnodes/simple/Chapter12Test.java | 281 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 642 ++++++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 82 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 556 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 106 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 556 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 28 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 20 +- .../java/com/seaofnodes/simple/Chapter23Test.java | 22 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../com/seaofnodes/simple/MemoryEncodingTest.java | 12 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 73 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../java/com/seaofnodes/simple/progs/antiDep1.smp | 2 +- .../java/com/seaofnodes/simple/progs/large.smp | 4 +- .../java/com/seaofnodes/simple/progs/person.smp | 2 +- .../java/com/seaofnodes/simple/progs/person21.smp | 2 +- .../java/com/seaofnodes/simple/progs/ptrand.smp | 2 +- .../com/seaofnodes/simple/progs/riscv_load.smp | 2 +- .../com/seaofnodes/simple/progs/sext_str_1.smp | 2 +- .../seaofnodes/simple/progs/sext_str_fold_away.smp | 2 +- .../simple/progs/sext_str_not_fold_away.smp | 2 +- .../simple/progs/sext_str_not_fold_away_2.smp | 2 +- .../com/seaofnodes/simple/progs/stringHash.smp | 6 +- .../com/seaofnodes/simple/progs/stringHash21.smp | 6 +- chapter24/README.md | 2 +- chapter24/docs/examples/BubbleSort.smp | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 130 +- .../src/main/java/com/seaofnodes/simple/Var.java | 1 + .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/CastNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 4 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 4 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 6 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 9 + .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 61 +- .../com/seaofnodes/simple/type/TypeStruct.java | 5 +- chapter24/src/main/smp/sys.smp | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 56 +- .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 - .../java/com/seaofnodes/simple/Chapter11Test.java | 310 +---- .../java/com/seaofnodes/simple/Chapter12Test.java | 281 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 363 +++-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 82 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 127 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 556 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 106 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 556 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 14 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 30 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 26 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 22 +- .../java/com/seaofnodes/simple/Chapter23Test.java | 24 +- .../java/com/seaofnodes/simple/Chapter24Test.java | 22 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../com/seaofnodes/simple/MemoryEncodingTest.java | 12 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 6 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 73 + .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- .../test/java/com/seaofnodes/simple/TypeTest.java | 10 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../java/com/seaofnodes/simple/progs/person21.smp | 2 +- .../com/seaofnodes/simple/progs/stringHash.smp | 6 +- .../com/seaofnodes/simple/progs/stringHash21.smp | 6 +- chapter25/Makefile | 2 +- chapter25/README.md | 4 + chapter25/ROADMAP.md | 16 +- chapter25/docs/examples/BubbleSort.smp | 2 +- chapter25/docs/examples/Dijkstra.smp | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 95 +- .../com/seaofnodes/simple/codegen/Serialize.java | 11 +- .../com/seaofnodes/simple/node/CallEndNode.java | 2 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../com/seaofnodes/simple/node/ConvertNode.java | 4 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 5 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 68 +- chapter25/src/main/smp/sys/aryi64.smp | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 36 +- .../java/com/seaofnodes/simple/Chapter11Test.java | 284 ---- .../java/com/seaofnodes/simple/Chapter12Test.java | 280 +++- .../java/com/seaofnodes/simple/Chapter13Test.java | 377 ++--- .../com/seaofnodes/simple/Chapter14FloatTest.java | 83 ++ .../java/com/seaofnodes/simple/Chapter14Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 32 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 120 +- .../java/com/seaofnodes/simple/Chapter17Test.java | 593 -------- .../java/com/seaofnodes/simple/Chapter17aTest.java | 109 ++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 593 ++++++++ .../java/com/seaofnodes/simple/Chapter18Test.java | 12 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 18 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 26 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 20 +- .../java/com/seaofnodes/simple/Chapter23Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter25Test.java | 23 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 2 +- .../com/seaofnodes/simple/MemoryEncodingTest.java | 14 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 6 +- .../java/com/seaofnodes/simple/MutabilityTest.java | 67 + .../test/java/com/seaofnodes/simple/TypeTest.java | 4 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- .../simple/spill/Chapter20Test-testString.smp | 8 +- .../simple/spill/Chapter21AllocTest-testString.smp | 6 +- .../simple/spill/Chapter21Test-testAntiDeps1.smp | 2 +- .../simple/spill/Chapter21Test-testInfinite.smp | 2 +- .../simple/spill/Chapter21Test-testPerson.smp | 2 +- .../spill/Chapter21Test-testStringExport.smp | 6 +- .../spill/Chapter22Test-testInfiniteReturn.smp | 2 +- .../simple/spill/Chapter22Test-testPerson.smp | 2 +- .../simple/spill/Chapter22Test-testSextFail.smp | 2 +- .../simple/spill/Chapter22Test-testSextFail2.smp | 2 +- .../simple/spill/Chapter22Test-testSextSuccess.smp | 2 +- .../simple/spill/Chapter23AllocTest-testString.smp | 6 +- .../simple/spill/Chapter23Test-testAndPtr.smp | 2 +- .../simple/spill/Chapter23Test-testFRefFields.smp | 2 +- .../simple/spill/Chapter23Test-testMethod.smp | 2 +- .../spill/Chapter23Test-testOrRhsLoopLocal.smp | 2 +- .../spill/Chapter23Test-testOrRhsLoopMemory.smp | 4 +- .../spill/Chapter24AllocTest-testBubbleSort.smp | 4 +- docs/chapter-backports.md | 93 +- graph/README.md | 10 +- pom.xml | 6 +- skills/chapter25-codex-notes.md | 28 +- transpile-tests/README.md | 4 + .../seaofnodes/simple/transpile/AstVisitor.java | 27 +- .../seaofnodes/simple/transpile/JUnitParser.java | 10 +- .../simple/transpile/TranspilerTest.java | 2 + 1124 files changed, 64681 insertions(+), 60842 deletions(-) create mode 100644 chapter10/Makefile create mode 100644 chapter10/README.md create mode 100644 chapter10/chapter10.iml create mode 100644 chapter10/docs/10-grammar.md create mode 100644 chapter10/docs/Makefile create mode 100644 chapter10/docs/example1.gv create mode 100644 chapter10/docs/example1.json create mode 100644 chapter10/docs/example1.svg create mode 100644 chapter10/docs/example2a.gv create mode 100644 chapter10/docs/example2a.json create mode 100644 chapter10/docs/example2a.svg create mode 100644 chapter10/docs/example2b.gv create mode 100644 chapter10/docs/example2b.json create mode 100644 chapter10/docs/example2b.svg create mode 100644 chapter10/docs/example2c.gv create mode 100644 chapter10/docs/example2c.json create mode 100644 chapter10/docs/example2c.svg create mode 100644 chapter10/docs/lattice.gv create mode 100644 chapter10/docs/lattice.svg create mode 100644 chapter10/docs/load-after-store-after.json create mode 100644 chapter10/docs/load-after-store-after.svg create mode 100644 chapter10/docs/load-after-store-before.json create mode 100644 chapter10/docs/load-after-store-before.svg create mode 100644 chapter10/docs/type-control.svg create mode 100644 chapter10/docs/type-integer.svg create mode 100644 chapter10/docs/type-memory.svg create mode 100644 chapter10/docs/type-pointer.svg create mode 100644 chapter10/docs/type-struct.svg create mode 100644 chapter10/docs/type-tuple.svg create mode 100644 chapter10/pom.xml create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/Parser.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/Utils.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/Node.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.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 create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/Field.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/Type.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java create mode 100644 chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java create mode 100644 chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java delete mode 100644 chapter10a/Makefile delete mode 100644 chapter10a/README.md delete mode 100644 chapter10a/chapter10a.iml delete mode 100644 chapter10a/docs/10-grammar.md delete mode 100644 chapter10a/docs/Makefile delete mode 100644 chapter10a/docs/example1.gv delete mode 100644 chapter10a/docs/example1.json delete mode 100644 chapter10a/docs/example1.svg delete mode 100644 chapter10a/docs/example2a.gv delete mode 100644 chapter10a/docs/example2a.json delete mode 100644 chapter10a/docs/example2a.svg delete mode 100644 chapter10a/docs/example2b.gv delete mode 100644 chapter10a/docs/example2b.json delete mode 100644 chapter10a/docs/example2b.svg delete mode 100644 chapter10a/docs/example2c.gv delete mode 100644 chapter10a/docs/example2c.json delete mode 100644 chapter10a/docs/example2c.svg delete mode 100644 chapter10a/docs/lattice.gv delete mode 100644 chapter10a/docs/lattice.svg delete mode 100644 chapter10a/docs/load-after-store-after.json delete mode 100644 chapter10a/docs/load-after-store-after.svg delete mode 100644 chapter10a/docs/load-after-store-before.json delete mode 100644 chapter10a/docs/load-after-store-before.svg delete mode 100644 chapter10a/docs/type-control.svg delete mode 100644 chapter10a/docs/type-integer.svg delete mode 100644 chapter10a/docs/type-memory.svg delete mode 100644 chapter10a/docs/type-pointer.svg delete mode 100644 chapter10a/docs/type-struct.svg delete mode 100644 chapter10a/docs/type-tuple.svg delete mode 100644 chapter10a/pom.xml delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/Parser.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/Utils.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/ProjNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/StartNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java delete mode 100644 chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter07Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter08Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java delete mode 100644 chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java delete mode 100644 chapter10b/Makefile delete mode 100644 chapter10b/README.md delete mode 100644 chapter10b/chapter10b.iml delete mode 100644 chapter10b/docs/10-grammar.md delete mode 100644 chapter10b/docs/Makefile delete mode 100644 chapter10b/docs/example1.gv delete mode 100644 chapter10b/docs/example1.json delete mode 100644 chapter10b/docs/example1.svg delete mode 100644 chapter10b/docs/example2a.gv delete mode 100644 chapter10b/docs/example2a.json delete mode 100644 chapter10b/docs/example2a.svg delete mode 100644 chapter10b/docs/example2b.gv delete mode 100644 chapter10b/docs/example2b.json delete mode 100644 chapter10b/docs/example2b.svg delete mode 100644 chapter10b/docs/example2c.gv delete mode 100644 chapter10b/docs/example2c.json delete mode 100644 chapter10b/docs/example2c.svg delete mode 100644 chapter10b/docs/lattice.gv delete mode 100644 chapter10b/docs/lattice.svg delete mode 100644 chapter10b/pom.xml delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/Parser.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/Utils.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MinusNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MulNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/MultiNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/NewNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/ProjNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/StartNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java delete mode 100644 chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter07Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter08Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java delete mode 100644 chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java create mode 100644 chapter11/chapter11.iml create mode 100644 chapter11/docs/10-grammar.md delete mode 100644 chapter11/docs/11-grammar.md create mode 100644 chapter11/docs/example1.gv create mode 100644 chapter11/docs/example1.json create mode 100644 chapter11/docs/example1.svg create mode 100644 chapter11/docs/example2a.gv create mode 100644 chapter11/docs/example2a.json create mode 100644 chapter11/docs/example2a.svg create mode 100644 chapter11/docs/example2b-final.json create mode 100644 chapter11/docs/example2b-final.svg create mode 100644 chapter11/docs/example2b-merged.json create mode 100644 chapter11/docs/example2b-merged.svg create mode 100644 chapter11/docs/example2b-split-y.json create mode 100644 chapter11/docs/example2b-split-y.svg create mode 100644 chapter11/docs/example2b.gv create mode 100644 chapter11/docs/example2b.json create mode 100644 chapter11/docs/example2b.svg create mode 100644 chapter11/docs/example2c.gv create mode 100644 chapter11/docs/example2c.json create mode 100644 chapter11/docs/example2c.svg delete mode 100644 chapter11/docs/graph1.gv delete mode 100644 chapter11/docs/graph1.json delete mode 100644 chapter11/docs/graph1.svg delete mode 100644 chapter11/docs/graph2.gv delete mode 100644 chapter11/docs/graph2.json delete mode 100644 chapter11/docs/graph2.svg delete mode 100644 chapter11/docs/graph3-snip.jpg delete mode 100644 chapter11/docs/graph3-snip.json delete mode 100644 chapter11/docs/graph3-snip.svg delete mode 100644 chapter11/docs/graph3.gv delete mode 100644 chapter11/docs/graph3.json delete mode 100644 chapter11/docs/graph3.svg delete mode 100644 chapter11/docs/graph4.gv delete mode 100644 chapter11/docs/graph4.json delete mode 100644 chapter11/docs/graph4.svg delete mode 100644 chapter11/docs/graph5.gv delete mode 100644 chapter11/docs/graph5.json delete mode 100644 chapter11/docs/graph5.svg delete mode 100644 chapter11/docs/graph6.gv delete mode 100644 chapter11/docs/graph6.json delete mode 100644 chapter11/docs/graph6.svg delete mode 100644 chapter11/docs/graph7.gv delete mode 100644 chapter11/docs/graph7.json delete mode 100644 chapter11/docs/graph7.svg delete mode 100644 chapter11/docs/graph8.gv delete mode 100644 chapter11/docs/graph8.json delete mode 100644 chapter11/docs/graph8.svg create mode 100644 chapter11/docs/lattice.gv create mode 100644 chapter11/docs/lattice.svg delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java delete mode 100644 chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java delete mode 100644 chapter11/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter12/chapter12.iml delete mode 100644 chapter12/docs/lattice.gv delete mode 100644 chapter12/docs/lattice.svg delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/CFGNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/CProjNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/NeverNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/OutNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java delete mode 100644 chapter12/src/main/java/com/seaofnodes/simple/type/TypeFloat.java delete mode 100644 chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter13/chapter13.iml create mode 100644 chapter13/docs/graph1.gv create mode 100644 chapter13/docs/graph1.json create mode 100644 chapter13/docs/graph1.svg create mode 100644 chapter13/docs/graph2.gv create mode 100644 chapter13/docs/graph2.json create mode 100644 chapter13/docs/graph2.svg create mode 100644 chapter13/docs/graph3-snip.jpg create mode 100644 chapter13/docs/graph3-snip.json create mode 100644 chapter13/docs/graph3-snip.svg create mode 100644 chapter13/docs/graph3.gv create mode 100644 chapter13/docs/graph3.json create mode 100644 chapter13/docs/graph3.svg create mode 100644 chapter13/docs/graph4.gv create mode 100644 chapter13/docs/graph4.json create mode 100644 chapter13/docs/graph4.svg create mode 100644 chapter13/docs/graph5.gv create mode 100644 chapter13/docs/graph5.json create mode 100644 chapter13/docs/graph5.svg create mode 100644 chapter13/docs/graph6.gv create mode 100644 chapter13/docs/graph6.json create mode 100644 chapter13/docs/graph6.svg create mode 100644 chapter13/docs/graph7.gv create mode 100644 chapter13/docs/graph7.json create mode 100644 chapter13/docs/graph7.svg create mode 100644 chapter13/docs/graph8.gv create mode 100644 chapter13/docs/graph8.json create mode 100644 chapter13/docs/graph8.svg delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java delete mode 100644 chapter13/src/main/java/com/seaofnodes/simple/type/TypeFloat.java delete mode 100644 chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter14/chapter14.iml delete mode 100644 chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter17/Makefile delete mode 100644 chapter17/README.md delete mode 100644 chapter17/docs/17-grammar.md delete mode 100644 chapter17/docs/Makefile delete mode 100644 chapter17/pom.xml delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/Ary.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/IRPrinter.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/Parser.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/Utils.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/CProjNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/CastNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ConstantNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/IfNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/LoopNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MinusFNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MinusNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MulFNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/MultiNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/NeverNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/Node.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/OutNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ProjNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/StructNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/SubFNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/Field.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/Type.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java delete mode 100644 chapter17/src/main/java/com/seaofnodes/simple/type/TypeTuple.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter01Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter02Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter03Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter05Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter06Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter07Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter08Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter09Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter10bTest.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/FuzzerWrap.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java delete mode 100644 chapter17/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java create mode 100644 chapter17a/Makefile create mode 100644 chapter17a/README.md create mode 100644 chapter17a/chapter17a.iml create mode 100644 chapter17a/docs/17-grammar.md create mode 100644 chapter17a/docs/Makefile create mode 100644 chapter17a/pom.xml create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/Ary.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/IRPrinter.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/Parser.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/Utils.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/AddFNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/AddNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/AndNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/BoolNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/CProjNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/CastNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/DivFNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/DivNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/IfNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/LoadNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/LoopNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MinusNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MulFNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MulNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/MultiNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/NeverNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/NewNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/Node.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/NotNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/OrNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/OutNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/PhiNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ProjNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/RegionNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/SarNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ShlNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ShrNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/StartNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/StopNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/StoreNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/StructNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/SubFNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/SubNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/XorNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/Field.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/Type.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeMem.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter01Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter02Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter03Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter04Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter05Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter06Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter07Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter08Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter09Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter10Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter11Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter13Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/MutabilityTest.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/SchedulerTest.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/TypeTest.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java create mode 100644 chapter17a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java create mode 100644 chapter17b/Makefile create mode 100644 chapter17b/README.md create mode 100644 chapter17b/chapter17b.iml create mode 100644 chapter17b/docs/17-grammar.md create mode 100644 chapter17b/docs/Makefile create mode 100644 chapter17b/pom.xml create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/Ary.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/IRPrinter.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/Parser.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/Utils.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/AddFNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/AddNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/AndNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/BoolNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/CProjNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/CastNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/DivFNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/DivNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/IfNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/LoadNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/LoopNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MinusNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MulFNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MulNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/MultiNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/NeverNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/NewNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/Node.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/NotNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/OrNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/OutNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/PhiNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ProjNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/RegionNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/SarNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ShlNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ShrNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/StartNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/StopNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/StoreNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/StructNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/SubFNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/SubNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/XorNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/Field.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/Type.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeMem.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter01Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter02Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter03Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter04Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter05Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter06Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter07Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter08Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter09Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter10Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter11Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter13Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/MutabilityTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/SchedulerTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/TypeTest.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java create mode 100644 chapter17b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java create mode 100644 chapter18/#README.md# delete mode 100644 chapter18/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter18/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter18/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter18/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter19/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter19/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter19/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter19/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter20/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter21/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter21/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter21/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter21/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter22/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter22/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter22/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter22/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter23/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter23/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter23/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter23/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter24/src/test/java/com/seaofnodes/simple/Chapter10bTest.java create mode 100644 chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter24/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter24/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter24/src/test/java/com/seaofnodes/simple/MutabilityTest.java delete mode 100644 chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java delete mode 100644 chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java diff --git a/.github/workflows/linearize.sh b/.github/workflows/linearize.sh index ef079bb8..7ce8e52c 100755 --- a/.github/workflows/linearize.sh +++ b/.github/workflows/linearize.sh @@ -83,6 +83,9 @@ while read -r chapter; do # This is the first commit; setup shared files. cp "$repo"/{README.md,pom.xml} . mkdir docs + # Shared type-domain illustrations are referenced from chapter READMEs. + cp "$repo"/docs/type-*.svg docs/ + git add docs/type-*.svg # Remove links to chapters. sed -Ei 's,\[Chapter ([0-9]+[a-z]?)\]\(chapter0?\1/README\.md\),Chapter \1,' README.md # Change to a JAR and delete the modules, for a single-project structure. @@ -107,6 +110,9 @@ while read -r chapter; do # Repair links for this chapter. sed -Ei 's,\bdocs/,,' "docs/$chapter/README.md" sed -Ei 's,\(\.\./isa/,(../../isa/,g' "docs/$chapter/README.md" + # Source links are chapter-relative in the main tree; sources live at the + # repository root in a standalone chapter checkout. + sed -Ei 's,\((\./)?src/,(../../src/,g' "docs/$chapter/README.md" git add README.md "docs/$chapter/README.md" pom.xml # Create a commit for the chapter, using the metadata. diff --git a/.gitignore b/.gitignore index ca705816..617579ba 100644 --- a/.gitignore +++ b/.gitignore @@ -24,10 +24,17 @@ TAGS # Libs */lib/*jar -# Portable IDEA descriptors for the split memory chapters -!chapter10a/chapter10a.iml -!chapter10b/chapter10b.iml +# Portable IDEA descriptors for the memory, scheduling and numeric chapters +!chapter10/chapter10.iml +!chapter11/chapter11.iml +!chapter12/chapter12.iml +!chapter13/chapter13.iml +!chapter14/chapter14.iml !graph/graph.iml !print/print.iml !isa/isa.iml !isa/src/test-support/isa-eval.iml + +# Portable descriptors for the mutability / syntax-sugar split +!chapter17a/chapter17a.iml +!chapter17b/chapter17b.iml diff --git a/README.md b/README.md index 0bc961f5..72a188c5 100644 --- a/README.md +++ b/README.md @@ -51,17 +51,17 @@ The Simple language is styled after a subset of C or Java. * [Chapter 7](chapter07/README.md): `while` statement; looping constructs - eager phi approach. * [Chapter 8](chapter08/README.md): Looping constructs continued, lazy phi creation, `break` and `continue` statements. * [Chapter 9](chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists. -* [Chapter 10a](chapter10a/README.md): User defined structs, pointers and null +* [Chapter 10](chapter10/README.md): User defined structs, pointers and null analysis. One memory value in SSA. Loads, stores, and an executable evaluator. -* [Chapter 10b](chapter10b/README.md): Equivalence class aliasing. Lazy memory +* [Chapter 11](chapter11/README.md): Equivalence class aliasing. Lazy memory partitioning with `MemMerge`, `MemPhi`, and `BulkMemPhi`. -* [Chapter 11](chapter11/README.md): Global Code Motion - Scheduling. -* [Chapter 12](chapter12/README.md): Float type. -* [Chapter 13](chapter13/README.md): Nested references in Structs. -* [Chapter 14](chapter14/README.md): Narrow primitive types (e.g. bytes) +* [Chapter 12](chapter12/README.md): Reference fields, forward references and recursive structs. +* [Chapter 13](chapter13/README.md): Global Code Motion - Scheduling. +* [Chapter 14](chapter14/README.md): Numeric types: floats, narrow integers, ranges and rounding to `f32`. * [Chapter 15](chapter15/README.md): One dimensional static length array type, with array loads and stores. * [Chapter 16](chapter16/README.md): Constructors -* [Chapter 17](chapter17/README.md): Mutability & Syntax Sugar: `var`, `val`, `x+=y`, `for(init; test; next) body` +* [Chapter 17a](chapter17a/README.md): Binding mutability, reference permissions, and deep read-only views. +* [Chapter 17b](chapter17b/README.md): Syntax sugar: `var`, `val`, `x+=y`, `for(init; test; next) body` * [Chapter 18](chapter18/README.md): Functions and calls. * [Chapter 19](chapter19/README.md): Instruction selection and portable compilation * [Chapter 20](chapter20/README.md): Graph Coloring Register Allocation @@ -74,7 +74,7 @@ The Simple language is styled after a subset of C or Java. ## Building across chapters The optional [interactive graph viewer](graph/README.md) is shared in `graph/`. -Chapters 1–25, including 10a and 10b, launch it with `make view` from the chapter directory. +Chapters 1–25 launch it with `make view` from the chapter directory. The [debug printers](print/README.md) are shared in `print/`; Chapter 2 needs only a one-line expression format. A shared `BaseNode` supplies identity and edge @@ -96,20 +96,16 @@ them; Maven's Java build alone does not produce those prerequisites. The [chapter backport queue](docs/chapter-backports.md) records proposed small corrections and the per-chapter test/review workflow. Larger architectural moves -are tracked separately there. The 10a/10b split is complete; forwarding 10b's -memory representation now runs through Chapter 24, including constructors, -mutability, whole-memory function calls, machine-code execution, cyclic field -types, and SCCP. Chapters 22-25 are ready for review. Chapter 25 includes the -related Load-search, Phi-factoring, and scheduling corrections while retaining -its separate constructor-memory and incomplete-type SSA design. +are tracked separately there. Chapters 10-14 now group whole memory, +lazy alias splitting, references, GCM, and numeric types in that order. +The memory representation is forwarded through Chapter 25. To build and test just the memory chapters: ```sh -make lib tests release CHAPTERS="chapter10a chapter10b" +make lib tests release CHAPTERS="chapter10 chapter11 chapter12" ``` -The root Maven reactor also includes both modules. Each chapter has its own -IDEA module descriptor and depends on the shared `graph` module. Chapter -directories with letter suffixes participate in the normal Make chapter discovery -and in the linear-history workflow. +The root Maven reactor includes every chapter. Chapters 10-14 have portable +IDEA module descriptors depending on the shared `graph` and `print` modules. +Make and the linear-history workflow discover the numbered chapter directories. diff --git a/TypeAnalysis.md b/TypeAnalysis.md index 425f6dbc..43160e20 100644 --- a/TypeAnalysis.md +++ b/TypeAnalysis.md @@ -215,7 +215,7 @@ values, and can observe that the 32b ones are a strict subset of the 64b ones. [Lattice3](./docs/lattice_if.svg) -First appears in [Simple chapter12](https://github.com/SeaOfNodes/Simple/tree/main/chapter12). +First appears in [Simple chapter14](https://github.com/SeaOfNodes/Simple/tree/main/chapter14). Now we add more "inner" lattices, the key being that inner lattices do not @@ -305,7 +305,7 @@ but we will shortly use them in building structs and functions. [Lattice6](./docs/lattice_iffct.svg) -First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a) +First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10) along with structs and pointers. @@ -337,7 +337,7 @@ desired semantics, as well as the normal *meet* on the field type. I am not covering here how to initialize struct members, or the syntax for declaring structs or traits - just the resulting types. -First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a) +First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10) along with tuples and pointers. @@ -377,7 +377,7 @@ Here the load to fetch the `makeSound` method fails during error reporting; in Simple this is just a `LoadNode` which has an error check against its pointer input being possibly `null`. -First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a), +First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10), along with structs and tuples. diff --git a/chapter02/README.md b/chapter02/README.md index 86b7aac0..f29c1cfd 100644 --- a/chapter02/README.md +++ b/chapter02/README.md @@ -80,7 +80,7 @@ optimizations such as: * The compiler may be able to identify regions of code that are dead and no longer needed, such as when a conditional branch always takes one of the branches ([Chapter 6](../chapter06/README.md)). -* Pointers may be known to be not-null, and a null check can be skipped ([Chapter 10a](../chapter10a/README.md)). +* Pointers may be known to be not-null, and a null check can be skipped ([Chapter 10](../chapter10/README.md)). * Array indices may be known to be in-range, and a range check can be skipped. * Many additional optimizations are possible when the compiler learns more about the possible set of Node values. @@ -138,7 +138,7 @@ starting in [Chapter 4](../chapter04/README.md) and [Chapter There are other important properties of the Lattice that we discuss in [Chapter -4](../chapter04/README.md) and [Chapter 10a](../chapter10a/README.md), such as the "meet" and "join" operators and their rules. +4](../chapter04/README.md) and [Chapter 10](../chapter10/README.md), such as the "meet" and "join" operators and their rules. ## Nodes Pre Peephole Optimization diff --git a/chapter03/README.md b/chapter03/README.md index ce5901ce..799d8c0a 100644 --- a/chapter03/README.md +++ b/chapter03/README.md @@ -65,9 +65,9 @@ A ScopeNode encapsulates the stack of symbol tables that are used to implement l * In [Chapter 5](../chapter05/README.md) when we implement branches, the ScopeNode gets duplicated at a branch point and then gets merged when the branch joins. This allows the names to evolve in each branch independently. At the merge point, names that have diverged require a Phi node. * In [Chapter 7](../chapter07/README.md) additional logic is deployed for handling Phis from back edges. -* The ScopeNode is used to track some other values such as the Control token starting from [Chapter 4](../chapter04/README.md), and whole memory starting from [Chapter 10a](../chapter10a/README.md). Special name bindings are used +* The ScopeNode is used to track some other values such as the Control token starting from [Chapter 4](../chapter04/README.md), and whole memory starting from [Chapter 10](../chapter10/README.md). Special name bindings are used for these, and each name is tracked across branches and then merged at merge points. -* In [Chapter 10a](../chapter10a/README.md) the ScopeNode is enhanced to also maintain the declared type for each name. +* In [Chapter 10](../chapter10/README.md) the ScopeNode is enhanced to also maintain the declared type for each name. From an implementation standpoint, each symbol table is represented as a `HashMap`, where the `String` is the variable name, and the `Integer` is an index into the inputs of the ScopeNode. To access the Node associated with a name, first the symbol table is consulted, and then the ScopeNode's inputs to find the required Node. diff --git a/chapter05/README.md b/chapter05/README.md index 426da6fb..1f7c240e 100644 --- a/chapter05/README.md +++ b/chapter05/README.md @@ -92,7 +92,7 @@ exists in a “sea” of Nodes, with little control structure. The “sea” of Nodes is useful for optimization, but does not represent any traditional intermediate representation such as a CFG. We need a way to serialize the graph and get back the control dependences. We do this with a -simple Global Code Motion [^3] algorithm in [Chapter 11](../chapter11/README.md). +simple Global Code Motion [^3] algorithm in [Chapter 13](../chapter13/README.md). #### `Stop` Nodes `StopNode`s only have `ReturnNode` inputs. They mark the program termination. diff --git a/chapter09/README.md b/chapter09/README.md index 5aa74a3b..1139060b 100644 --- a/chapter09/README.md +++ b/chapter09/README.md @@ -1,7 +1,7 @@ # Chapter 9: Global Value Numbering and Iterative Peepholes [Previous: Chapter 8](../chapter08/README.md) | -[Next: Chapter 10a](../chapter10a/README.md) +[Next: Chapter 10](../chapter10/README.md) # Table of Contents diff --git a/chapter10/Makefile b/chapter10/Makefile new file mode 100644 index 00000000..5ed21084 --- /dev/null +++ b/chapter10/Makefile @@ -0,0 +1,7 @@ +# Standalone compiler snapshot; shared rules also work in linearized checkouts. +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) +test_cp += com.seaofnodes.simple.FuzzerWrap + +.PHONY: fuzzer +fuzzer: $(test_classes) + java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter10/README.md b/chapter10/README.md new file mode 100644 index 00000000..219d178f --- /dev/null +++ b/chapter10/README.md @@ -0,0 +1,420 @@ +# Chapter 10: Structs and Memory + +[Previous: Chapter 9](../chapter09/README.md) | +[Next: Chapter 11](../chapter11/README.md) + +This chapter adds user-defined structs, pointers, allocation, field loads and +stores, and null checking. It also introduces an evaluator that executes the +graph, including its memory effects. The [grammar](docs/10-grammar.md) is the +same in Chapters 10 and 11; the next chapter improves the memory representation. + +## References + +Structs are a collection of named and typed fields; very classically similar to +C or Java. New structs are made with the `new` keyword; this allocates memory +and produces a reference. Freeing memory awaits a later chapter. Fields are +accessed with the `.` notation. The fields can hold any scalar value, +including reference to other structs and be mutually recursive. Any reference +field can allow null or not, same as a normal variable. + +```java +struct Person { + String last; // Last name, must exist + String? middle; // Optional middle name + int age; +}; +``` + +Here we have fields `last`, a not-null final String, and `middle` a nullable +and final String, and an `int age`. Like all `int` declared fields, it is +mutable. Later chapters will allow both mutable reference fields and immutable +integer fields; for now this choice covers most of the common ground. + +You have to have a reference to get at the fields, static fields are added in a later chapter. + +All field references have to be against not-null pointers, and this is directly +checked in the type system. I.e., Simple disallows null-pointer-exceptions by +design. Some examples of null checking before using: + +```java +print(ptr.age); // ERROR! ptr might be null + +if( ptr ) print(ptr.age); // Test before use + +// Field use can be far removed, as long as the check dominates +if( ptr ) { + S1; // Other statements + while( other ) { ...ptr.age... } +}; +``` + +Allocation, as well as Stores, means we now have memory effects to deal with. + + +## One memory value + +Arithmetic nodes depend on the values they use, similarly a load also depends +on when it observes memory. In this chapter every store consumes the preceding +memory state and produces the next one, and a load consumes memory without +changing it. This makes the source program's memory ordering explicit in the +graph. + +```java +struct Point { int x; int y; } +Point p = new Point; +p.x = 3; +p.y = arg; +return p.x; +``` + +`NewNode` produces the pointer. The parser emits stores of zero for both fields, +then stores 3 into `x` and `arg` into `y`. The final load observes the memory +after the `y` store. We deliberately keep that dependency here, even though +different fields are independent. Chapter 11 will remove it. + +![One chain orders stores and loads](docs/example1.svg) + +The graphs show dependencies before local folding. To focus on memory, remove +most control edges leaving behind minimal True/False nodes when merges are +important. Also, the layout shows dependencies, not an execution schedule. + +| Node | Inputs | Result | +|----------|---|---| +| `New` | Control and a struct type | A fresh, non-null object pointer | +| `Store` | Memory, pointer, field name, value | The updated whole-memory state | +| `Load` | Memory, pointer, field name | The value of the field | +| `Start` | External entry | Control, initial memory, and `arg` | +| `Return` | Control, memory, result | Completion with all preceding effects retained | + +For nodes consuming or producing both control and memory, control occupies slot +0 and memory slot 1. Start's outputs are `{ctrl, $mem, arg}`; Return's inputs +are `{ctrl, $mem, result}`. + +The memory edge is a dependency, not a copy of the heap. The graph determines +the legal execution order. In hardware, a Store updates physical memory +directly. In the evaluator, a Store updates an object's attached memory array. + +## Memory is an SSA variable + +The parser keeps one hidden binding named `$mem`. Source identifiers cannot +contain `$`, so user code cannot name it. Reading and updating memory uses the +same `ScopeNode` lookup and update operations as a local variable. + +For an `if`, the two branches can finish with different memory values. An +ordinary Phi selects the memory from the branch that executed: + +```java +struct Point { int x; int y; } +Point p = new Point; +if( arg ) p.x = 3; else p.x = 4; +return p.x; +``` + +![Whole memory across a branch](docs/example2a.svg) + +Loops reuse Chapter 8's lazy Phi construction. Reading or updating `$mem` in a +loop creates an initially incomplete memory Phi. Closing the loop supplies its +backedge. `break`, `continue`, nested loops, and early returns use the same scope +mechanics as scalar variables. + +```java +struct Point { int x; int y; } +Point p = new Point; +while( arg ) { + p.x = arg; + arg = arg-1; +}; +return p.x; +``` + +![Whole memory around a loop](docs/example2b.svg) + +Every Return consumes the current whole-memory value. This keeps stores alive +even when the returned scalar does not depend on them, or when the program +returns the modified object itself. + +## Enhanced Type Lattice + +The Type Lattice for Simple has a major revision in this chapter. + +![Type lattice with six domains and parallel named struct alternatives](./docs/lattice.svg) + +The diagram uses `⊤` for **top** and `⊥` for **bottom**. A suffix names the +domain: `⊤:struct` is *struct top*, while the bare `⊤` is global top. In the +prose we use those words; in code examples we use the actual Java constants. +The implementation's `$TOP` and `$BOT` names need not appear in the diagram: + +| Diagram | Prose | Java constant | Internal struct name | +|---|---|---|---| +| `⊤:struct` | struct top | `TypeStruct.TOP` | `$TOP` | +| `⊥:struct` | struct bottom | `TypeStruct.BOT` | `$BOT` | + +Edges in this diagram show lattice order, with top above bottom. Boxes +containing an ellipsis abbreviate further parallel alternatives; they are not +extra union types. Field and tuple-element type lattices are elided in the +diagram. Field types can be any scalar type (Integer or Pointer), Tuple types +can be *any* lattice type, including Control and Memory. + +Within the type lattice, we now have the following domains: + +* ![Control](docs/type-control.svg) represents control flow: unreachable control + at control top, and reachable control at control bottom. +* ![Integer](docs/type-integer.svg) has integer top, integer bottom, and parallel + constants between them. The ellipses stand for the other integer constants. +* ![Pointer](docs/type-pointer.svg) (new) combines a struct type with nullability. + * The prefix `*` means pointer-to: `*Person` is a non-null pointer to a Person. + The suffix `?` includes null, so `*Person?` permits a Person pointer or null. + * The pointer domain contains the whole struct lattice twice, once without + null and once with it. The diagram expands named alternatives in both + copies. It is not just two individual pointer types. + * `null` combines struct top with the nullable flag: it has no non-null + object alternative. The diagram labels it `*⊤:struct?` as well as `null`. + * A pointer to struct bottom can point to any struct. The diagram shows the + non-null form as `*⊥:struct` and the nullable form as `*⊥:struct?`. + The latter is pointer bottom; pointer top is `*⊤:struct`. + * Null evaluates to false; non-null pointers evaluate to true. + + > C analogy: the non-null pointer to struct bottom is akin to `void *`, except + > that C's `void *` may also be null. Simple names this constant + > `TypeMemPtr.VOIDPTR` and prints it as `*void`. Struct bottom itself is the + > common lower bound of struct types, not C's `void` type. + +* ![Struct](docs/type-struct.svg) (new) represents user-defined named structs. + Fields have integer types in this chapter. + * Struct top lies above every named struct; struct bottom lies below every + named struct. Top is the high/empty struct alternative, while bottom + conservatively includes any struct name. + * `Person`, `s1`, `s2`, and further names are parallel alternatives. There is + no inheritance, so different named structs do not nest beneath one another, + even if they have identical fields. + * There is no fixed bound on the names a program can declare. The diagram's + ellipsis stands for more named alternatives, not a distinguished unnamed + struct. Each named box abbreviates its own field-type lattice. +* ![Memory](docs/type-memory.svg) (new) represents the whole-memory token. + Memory top and memory bottom are `TypeMem.TOP` and `TypeMem.BOT`. Neither + records the values stored in individual fields. +* ![Tuple](docs/type-tuple.svg) represents a collection of results from one node, + such as Start's control, memory, and argument. The pink headers identify tuples + of different lengths; each vertically stacked black `type` cell stands for + the full type lattice again, including the possibility of another tuple. + +We use the following operations on the lattice: + +* **Meet** computes the greatest lower bound. For these examples, think of + combining the alternatives represented by its inputs. + * The meet of `Person` and `s1` is struct bottom. There is no separate type + representing exactly those two names. + * The meet of `*Person` and `*s1` is the non-null pointer to struct bottom. + * The meet of `*Person` and `null` is `*Person?`. + * The meet of the non-null pointer to struct bottom and `null` is pointer bottom. + * The meet of `1` and `2` is integer bottom. +* **Dual** reflects the lattice about its centerline: top and bottom exchange + places, and applying dual twice returns the original type. + * Struct top and struct bottom are duals. + * `null` and the non-null pointer to struct bottom are duals. + * A pointer's dual also duals its struct's fields and flips nullability. Thus + the dual of `*Person` is not generally the original `*Person?`: the field + types must be dualed too. Those field details are omitted in the diagram. +* **Join** computes the least upper bound. It is defined through meet and dual: + `JOIN(x,y) = MEET(x.dual(),y.dual()).dual()`. + * The join of `Person` and `s1` is struct top. + * The join of `*Person` and `*s1` is pointer top. + * The join of the non-null pointer to struct bottom and `null` is pointer top. + * The join of `1` and `2` is integer top. + +As we construct the Sea of Nodes graph, we ensure that values stay inside the +domain they are created in - that is pointer fields will never contain integers +(obviously)... nor even the generic `TOP` and `BOT`. There are a couple of +nuances worth highlighting. + +* When Phis are created, the initial type of the Phi is based on the declared + type of its first input. This is a pessimistic type assignment because we do + not yet know what other types will be met. + +* When all the inputs of the Phi are known, we start with the local Top of the + declared type, and then compute a meet of all the input types. This + computation results in a more refined type for the Phi. + +## Parser Enhancements + +In previous chapters the only available type was Integer. Now, variables can +be of Integer type or pointer to Struct types. To support this, the Parser now +tracks the declared type of a variable (used to only be Integer!). The +declared type of a variable defines the set of values that can be legitimately +assigned to the variable. The Sea of Nodes graph also tracks the actual type +of values assigned to variables, these type transitions are defined by the Type +lattice described in the previous section. + +## Null Pointer Analysis + +The Simple language syntax allows a pointer variable to be specified as +null-able - and it prevents ever throwing a Null Pointer Exception. The +compiler decides if a null check is needed, and if needed and missing +the compiler rejects the program (asking the user to insert a null check). + +When the Parser encounters conditions that test the truthiness of a pointer variable, it uses this knowledge to refine the type information in the branches that follow. + +Here are two motivating examples: + +```java +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +if( bar ) bar.a = 1; +``` + +If `bar` is not `null` above, then we would like to allow the assignment to +`bar.a`. If the final line lacked the null check, e.g. a plain `bar.a = 1;`, +then `Java` would compile this program but throw a NPE, and Simple rejects the +program pointing out a null check is required. + + +```java +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +int rez = 3; +if( !bar ) rez=4; +else bar.a = 1; +``` + +If `bar` is `null` then we would like `!bar` to evaluate to `true`. In the else +branch we know `bar` is not null, hence the assignment to `bar.a` should be +allowed. + +To enable this behaviour, we make the following enhancements. + +* We track the null-ness of a pointer in the Type system, and hence in every + Node that ptr type flows through. + +* We "wrap" the predicate of an `If` node with an up-cast when we see that the + predicate is a ptr value. The up-cast removes the null possibility on the + true branch making it legal to use the pointer. + +* The up-cast uses a `Cast` with the non-null pointer to struct bottom + (`TypeMemPtr.VOIDPTR`). It joins the original pointer type with that type, + corresponding to `*⊥:struct` in the diagram. This JOIN operation preserves + everything we already know about ptr, and also adds the new knowledge that + the ptr is not-null. + +* The up-cast is represented by the `CastNode` op, and if applicable, we + replace all occurrences of the original predicate with the up-cast in the + current scope. The change is local to each branch of the `If`; occurring + separately for the true and the false branches of the `If` (and the false + branch inverts the predicate). + +* The up-cast peepholes normally; if its input's type is a super type of the + up-cast, then the `CastNode` can be removed. See `ScopeNode.upcast()` method, + and `CastNode`. + +* When computing a type for Not, we produce an Integer type when its input is a + ptr. When the input is a `null` we convert to `1` and when it is a non-null + ptr value, we convert to `0`. See `NotNode.compute()`. + + +## Local memory optimizations + +A load immediately following a store to the **same pointer and field** can use +the stored value. We just wrote `arg` into `p.x`, so reading it back needs no +memory access: + +```java +struct Box { int x; } +Box p = new Box; +p.x = arg; +return p.x; // Can return arg directly. +``` + +| Before: read back the stored value | After: use the value directly | +|---|---| +| ![Before: Return reads Load p.x after Store p.x](docs/load-after-store-before.svg) | ![After: Return uses arg and still consumes the Store's memory](docs/load-after-store-after.svg) | + +Return's result input changes from the Load to `arg`, and the unused Load +disappears. Return still consumes the Store's memory, so the write is retained. + +A store immediately following another store to the same address can also +discard the earlier store when no other user needs it. That separate rewrite +can remove the zero-initialization store still shown above. Again, both the +pointer and field checks matter. + +A load can also move up through a memory Phi. This extends +[Chapter 5's push-through-Phi optimization](../chapter05/README.md#pushing-addition-up-through-a-phi): +there, arithmetic folds on the incoming paths; here, a load can fold against a +matching store. "A Load of a Phi becomes a Phi of Loads". In graph notation: + +```text +Load(Phi(r, m0, m1), p.x) + -> Phi(r, Load(m0, p.x), Load(m1, p.x)) +``` + +The Phi of memory becomes a Phi of loaded values. Since this peep increases +the overall Node count, the profit heurstic requires at least one Load to fold. +On a loop, that Load must be the **backedge**: folding only on entry would +leave a load that could be pushed around the cycle indefinitely. As with the +arithmetic rewrite, exposing a fold is part of deciding to transform the +graph, not just a hoped-for cleanup afterward. See +[`LoadNode.idealize()`](src/main/java/com/seaofnodes/simple/node/LoadNode.java). + +The reverse direction extends +[Chapter 5's common-operation pull-down](../chapter05/README.md#example-3): +"A Phis of Loads becomes a Load of a Phi". A Phi of matching Loads or Stores +can become one operation after the join. For example, these two writes: + +```java +if (arg) s.x = arg+1; +else s.x = arg+2; +``` + +can become `s.x = Phi(arg+1,arg+2)` and eventually `s.x = arg+Phi(1,2)`. The +general rewrite creates a separate, correctly typed Phi for each differing +operand, including memory and pointers. We must be more careful here with +memory ops; it is possible to lose some type precision, so we check for that as +part of the profitability. + +Also, a Store must have no other users, so moving it removes the original +effect. A Load must have no possible conflicting writes or memory merges among +its memory's users; otherwise delaying the read could change its value. Much +later, we will be adding anti-dependences, but for now we do a conservative +check. + +### Missing an easy one + +The single memory chain is correct but conservative. A store to `y` can +prevent forwarding an earlier store to `x`, even when the objects are +identical. This is a missed optimization and it motivates the next chapter. + +## Executing the graph + +The evaluator includes a small late scheduler. A load must execute before a +later store that consumes the same memory definition, even though there is no +ordinary data edge from the load to that store. These load/store +anti-dependencies supplement the explicit graph edges. + +For example, after allocating `p`, `int old = p.x; p.x = arg; return old;` +reads the value before the final store. In the graph below, Load and Store +consume the same memory state. The scheduler must execute the Load first; +there is no ordinary data edge between them. + +![A load must precede a clobbering store](docs/example2c.svg) + +The scheduler is sufficient to execute this chapter. Chapter 13 develops +global code motion and scheduling as a separate topic. + +## Build and test + +From this directory, with JDK 21 or newer, Make, and a shell: + +```sh +make lib +make tests +make release +make view +``` + +Make builds this snapshot directly, without Maven. `make release` produces +`build/release/simple.jar`; `make view` launches the shared interactive graph +viewer. Maven and IDEA module descriptions are also supplied. Tests include all +earlier chapters, memory ordering, returned object contents, and nested loops. + +Next, [Chapter 11](../chapter11/README.md) keeps this one parser binding while +splitting the graph into independent memory slices. diff --git a/chapter10/chapter10.iml b/chapter10/chapter10.iml new file mode 100644 index 00000000..a352db38 --- /dev/null +++ b/chapter10/chapter10.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chapter10/docs/10-grammar.md b/chapter10/docs/10-grammar.md new file mode 100644 index 00000000..b76531d5 --- /dev/null +++ b/chapter10/docs/10-grammar.md @@ -0,0 +1,125 @@ +# Grammar for Chapter 10 + +```antlrv4 +grammar SimpleLanguage; + +program + : statement+ EOF + ; + +statement + : returnStatement + | structDeclaration + | declStatement + | blockStatment + | expressionStatement + | ifStatement + | whileStatement + | breakStatement + | continueStatment + ; + +field + : 'int' IDENTIFIER ';' + ; + +fields + : field+ + ; + +structDeclaration + : 'struct' IDENTIFIER '{' fields '}' + ; + +whileStatement + : 'while' '(' expression ')' statement + ; + +breakStatement + : 'break' ';' + ; + +continueStatement + : 'continue' ';' + ; + +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + + +expressionStatement + : IDENTIFIER '=' expression ';' + | fieldExpression '=' expression ';' + ; + +blockStatement + : '{' statement+ '}' + ; + +structName + : IDENTIFIER + ; + +declStatement + : 'int' IDENTIFIER '=' expression ';' + | structName ('?')? IDENTIFIER '=' expression ';' + ; + +returnStatement + : 'return' expression ';' + ; + +expression + : comparisonExpression + ; + +comparisonExpression + : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-') unaryExpression + | '!' unaryExpression + | primaryExpression + ; + +newExpression + : 'new' IDENTIFIER + ; + +fieldExpression + : primaryExpresson '.' IDENTIFIER + ; + +primaryExpression + : IDENTIFIER + | INTEGER_LITERAL + | 'true' + | 'false' + | 'null' + | newExpression + | '(' expression ')' + | fieldExpression + ; + +INTEGER_LITERAL + : [1-9][0-9]* + | [0] + ; + +IDENTIFIER + : NON_DIGIT (NON_DIGIT | DIGIT)* + ; + +NON_DIGIT: [a-zA-Z_]; +DEC_DIGIT: [0-9]; +``` diff --git a/chapter10/docs/Makefile b/chapter10/docs/Makefile new file mode 100644 index 00000000..6fa0f057 --- /dev/null +++ b/chapter10/docs/Makefile @@ -0,0 +1 @@ +include ../../graph/docs.mk diff --git a/chapter10/docs/example1.gv b/chapter10/docs/example1.gv new file mode 100644 index 00000000..9585b8e4 --- /dev/null +++ b/chapter10/docs/example1.gv @@ -0,0 +1,16 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + M0 [label="Initial memory" shape=octagon style="filled" fillcolor="#dcecff"]; + X [label="Store p.x = 3" shape=octagon style="filled" fillcolor="#dcecff"]; + Y [label="Store p.y = arg" shape=octagon style="filled" fillcolor="#dcecff"]; + L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + L -> Y -> X -> M0 [color="#2867b2"]; + Return -> Y [color="#2867b2"]; Return -> L; + Return [shape=box style="filled" fillcolor="#fff1c4"]; +} diff --git a/chapter10/docs/example1.json b/chapter10/docs/example1.json new file mode 100644 index 00000000..a111ae88 --- /dev/null +++ b/chapter10/docs/example1.json @@ -0,0 +1,23 @@ +{ + "ver": 1, + "comp": "chapter10/docs/example1.svg", + "step": 1, + "roots": [13], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 11, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 12, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/example1.svg b/chapter10/docs/example1.svg new file mode 100644 index 00000000..d98e0ccd --- /dev/null +++ b/chapter10/docs/example1.svg @@ -0,0 +1,118 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[0] → #1 · ASSOC#10[1] → #8 · MEM#10[2] → #5 · DATA#10[3] → #9 · DATA#11[1] → #10 · MEM#11[2] → #5 · DATA#11[3] → #4 · DATA#12[1] → #11 · MEM#12[2] → #5 · DATA#13[1] → #11 · MEM#13[2] → #12 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8#8 Store p.y +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #6 DATA0123#3  #9#9 #3 +Type not computed + +[0] → #1 ASSOC0Store p.x  #10#10 Store p.x +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → #9 DATA0123Store p.y  #11#11 Store p.y +Type not computed + +[0] → — CTRL +[1] → #10 MEM +[2] → #5 DATA +[3] → #4 DATA0123Load p.x  #12#12 Load p.x +Type not computed + +[0] → — CTRL +[1] → #11 MEM +[2] → #5 DATA +[3] → — DATA0123Return  #13#13 Return +Type not computed + +[0] → — CTRL +[1] → #11 MEM +[2] → #12 DATA012 diff --git a/chapter10/docs/example2a.gv b/chapter10/docs/example2a.gv new file mode 100644 index 00000000..93a8a904 --- /dev/null +++ b/chapter10/docs/example2a.gv @@ -0,0 +1,17 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + M [label="Incoming memory" shape=octagon style="filled" fillcolor="#dcecff"]; + A [label="True: Store x" shape=octagon style="filled" fillcolor="#dcecff"]; + B [label="False: Store x" shape=octagon style="filled" fillcolor="#dcecff"]; + P [label="Phi(memory)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; + P -> A -> M [color="#2867b2"]; P -> B -> M [color="#2867b2"]; + P -> Region [style=dashed,color="#9396a3"]; Return -> P [color="#2867b2"]; + Return [shape=box style="filled" fillcolor="#fff1c4"]; + Region [shape=trapezium style="filled" fillcolor="#fff1c4"]; +} diff --git a/chapter10/docs/example2a.json b/chapter10/docs/example2a.json new file mode 100644 index 00000000..660c72e8 --- /dev/null +++ b/chapter10/docs/example2a.json @@ -0,0 +1,26 @@ +{ + "ver": 1, + "comp": "chapter10/docs/example2a.svg", + "step": 1, + "roots": [19], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 11, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, + {"id": 12, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 13, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 10, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 12, "role": "DATA", "label": null}], "proj": null}, + {"id": 14, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 11, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 14, "role": "DATA", "label": null}], "proj": null}, + {"id": 17, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 13, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null}, + {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/example2a.svg b/chapter10/docs/example2a.svg new file mode 100644 index 00000000..b7664907 --- /dev/null +++ b/chapter10/docs/example2a.svg @@ -0,0 +1,130 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#12[0] → #1 · ASSOC#13[0] → #10 · CTRL#13[1] → #8 · MEM#13[2] → #5 · DATA#13[3] → #12 · DATA#14[0] → #1 · ASSOC#15[0] → #11 · CTRL#15[1] → #8 · MEM#15[2] → #5 · DATA#15[3] → #14 · DATA#17[1] → #13 · MEM#17[2] → #15 · MEM#18[1] → #17 · MEM#18[2] → #5 · DATA#19[1] → #17 · MEM#19[2] → #18 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8#8 Store p.y +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #6 DATA0123True  #10/0#10 True +Projection 0 of #0 +Type not computed + +False  #11/1#11 False +Projection 1 of #0 +Type not computed + +#3  #12#12 #3 +Type not computed + +[0] → #1 ASSOC0Store p.x  #13#13 Store p.x +Type not computed + +[0] → #10 CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → #12 DATA0123#4  #14#14 #4 +Type not computed + +[0] → #1 ASSOC0Store p.x  #15#15 Store p.x +Type not computed + +[0] → #11 CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → #14 DATA0123Phi $mem  #17#17 Phi $mem +Type not computed + +[0] → — ASSOC +[1] → #13 MEM +[2] → #15 MEM012Load p.x  #18#18 Load p.x +Type not computed + +[0] → — CTRL +[1] → #17 MEM +[2] → #5 DATA +[3] → — DATA0123Return  #19#19 Return +Type not computed + +[0] → — CTRL +[1] → #17 MEM +[2] → #18 DATA012 diff --git a/chapter10/docs/example2b.gv b/chapter10/docs/example2b.gv new file mode 100644 index 00000000..c7d64509 --- /dev/null +++ b/chapter10/docs/example2b.gv @@ -0,0 +1,19 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + P [label="Phi(memory)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; + S [label="Loop-body Store" shape=octagon style="filled" fillcolor="#dcecff"]; + P -> Entry [color="#2867b2"]; + P -> S [color="#2867b2",constraint=false]; // Backedge must not place Store before Phi. + S -> P [color="#2867b2"]; + P -> Loop [style=dashed,color="#9396a3"]; Return -> P [color="#2867b2"]; + Return -> S [style=invis]; // Layout only: Phi, Store, Return from left to right. + Return [shape=box style="filled" fillcolor="#fff1c4"]; + Entry [shape=octagon style="filled" fillcolor="#dcecff"]; + Loop [shape=box style="rounded,filled" fillcolor="#ffe0ab"]; +} diff --git a/chapter10/docs/example2b.json b/chapter10/docs/example2b.json new file mode 100644 index 00000000..da2fc557 --- /dev/null +++ b/chapter10/docs/example2b.json @@ -0,0 +1,27 @@ +{ + "ver": 1, + "comp": "chapter10/docs/example2b.svg", + "step": 1, + "roots": [19], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null}, + {"id": 11, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 17, "role": "DATA", "label": null}], "proj": null}, + {"id": 13, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 14, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, + {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 13, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, + {"id": 16, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 17, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "DATA", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, + {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 14, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/example2b.svg b/chapter10/docs/example2b.svg new file mode 100644 index 00000000..1feb91a3 --- /dev/null +++ b/chapter10/docs/example2b.svg @@ -0,0 +1,135 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#10[1] → #8 · MEM#10[2] → #15 · MEM#11[1] → #4 · DATA#11[2] → #17 · DATA#15[0] → #13 · CTRL#15[1] → #10 · MEM#15[2] → #5 · DATA#15[3] → #11 · DATA#16[0] → #1 · ASSOC#17[1] → #11 · DATA#17[2] → #16 · DATA#18[0] → #14 · CTRL#18[1] → #10 · MEM#18[2] → #5 · DATA#19[1] → #10 · MEM#19[2] → #18 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8#8 Store p.y +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #6 DATA0123Phi $mem  #10#10 Phi $mem +Type not computed + +[0] → — ASSOC +[1] → #8 MEM +[2] → #15 MEM012Phi arg  #11#11 Phi arg +Type not computed + +[0] → — ASSOC +[1] → #4 DATA +[2] → #17 DATA012True  #13/0#13 True +Projection 0 of #0 +Type not computed + +False  #14/1#14 False +Projection 1 of #0 +Type not computed + +Store p.x  #15#15 Store p.x +Type not computed + +[0] → #13 CTRL +[1] → #10 MEM +[2] → #5 DATA +[3] → #11 DATA0123#1  #16#16 #1 +Type not computed + +[0] → #1 ASSOC0-  #17#17 - +Type not computed + +[0] → — CTRL +[1] → #11 DATA +[2] → #16 DATA012Load p.x  #18#18 Load p.x +Type not computed + +[0] → #14 CTRL +[1] → #10 MEM +[2] → #5 DATA +[3] → — DATA0123Return  #19#19 Return +Type not computed + +[0] → — CTRL +[1] → #10 MEM +[2] → #18 DATA012 diff --git a/chapter10/docs/example2c.gv b/chapter10/docs/example2c.gv new file mode 100644 index 00000000..f3a52b4d --- /dev/null +++ b/chapter10/docs/example2c.gv @@ -0,0 +1,14 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + M [label="Prior memory" shape=octagon style="filled" fillcolor="#dcecff"]; + L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + S [label="Store p.x" shape=octagon style="filled" fillcolor="#dcecff"]; + L -> M [color="#2867b2"]; S -> M [color="#2867b2"]; + S -> L [style=dashed,xlabel="execute load first"]; +} diff --git a/chapter10/docs/example2c.json b/chapter10/docs/example2c.json new file mode 100644 index 00000000..8623ab8d --- /dev/null +++ b/chapter10/docs/example2c.json @@ -0,0 +1,21 @@ +{ + "ver": 1, + "comp": "chapter10/docs/example2c.svg", + "step": 1, + "roots": [11], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/example2c.svg b/chapter10/docs/example2c.svg new file mode 100644 index 00000000..f7f7bce3 --- /dev/null +++ b/chapter10/docs/example2c.svg @@ -0,0 +1,109 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #8 · MEM#9[2] → #5 · DATA#10[1] → #8 · MEM#10[2] → #5 · DATA#10[3] → #4 · DATA#11[1] → #10 · MEM#11[2] → #9 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8#8 Store p.y +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #6 DATA0123Load p.x  #9#9 Load p.x +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → — DATA0123Store p.x  #10#10 Store p.x +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → #4 DATA0123Return  #11#11 Return +Type not computed + +[0] → — CTRL +[1] → #10 MEM +[2] → #9 DATA012 diff --git a/chapter10/docs/lattice.gv b/chapter10/docs/lattice.gv new file mode 100644 index 00000000..b56a3073 --- /dev/null +++ b/chapter10/docs/lattice.gv @@ -0,0 +1,127 @@ +digraph Lattice { + rankdir=TB; + newrank=true; + // Preserve the same-rank ordering edges through cluster layout. + remincross=false; + nodesep=0.22; + ranksep=0.65; + node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; + edge [color="#555555", arrowsize=0.6]; + + // These arrows are lattice-order edges, not use/definition edges. + TOP [label="⊤", fillcolor=black, fontcolor=white]; + BOT [label="⊥", fillcolor=black, fontcolor=white]; + + subgraph cluster_control { + label="Control"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffff00"]; + XCTRL [label="⊤:ctrl"]; + CTRL [label="⊥:ctrl"]; + XCTRL -> CTRL; + } + + subgraph cluster_integer { + label="Integer"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#add8e6"]; + INT_TOP [label="⊤:int"]; + INT_BOT [label="⊥:int"]; + INT_NEG [label="… −2, −1"]; + INT_ZERO [label="0"]; + INT_POS [label="1, 2, …"]; + {rank=same; INT_NEG; INT_ZERO; INT_POS;} + INT_NEG -> INT_ZERO -> INT_POS [style=invis]; + INT_TOP -> {INT_NEG INT_ZERO INT_POS}; + {INT_NEG INT_ZERO INT_POS} -> INT_BOT; + } + + subgraph cluster_pointer { + label="Pointer = nullability × struct type"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#008000", fontcolor=white]; + PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; + PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; + PTR_NULL [label="null"]; + PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; + PTR_PERSON [label="*Person"]; + PTR_S1 [label="*s1"]; + PTR_MORE [label="*s2, …"]; + PTR_PERSON_NIL [label="*Person?"]; + PTR_S1_NIL [label="*s1?"]; + PTR_MORE_NIL [label="*s2?, …"]; + {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} + {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} + PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; + PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; + PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; + {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; + PTR_PERSON -> PTR_PERSON_NIL; + PTR_S1 -> PTR_S1_NIL; + PTR_MORE -> PTR_MORE_NIL; + PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; + {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; + } + + subgraph cluster_struct { + label="Struct\nnamed collection of named scalar types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#90ee90"]; + STRUCT_TOP [label="⊤:struct\n[struct top]"]; + STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; + STRUCT_PERSON [label="Person\nage: int", shape=box]; + STRUCT_S1 [label="s1\nx: int", shape=box]; + STRUCT_S2 [label="s2\nx: int, y: int", shape=box]; + STRUCT_MORE [label="…", shape=box]; + {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} + STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; + STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; + {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; + } + + subgraph cluster_memory { + label="Memory"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#00008b", fontcolor=white]; + MEM_TOP [label="⊤:mem"]; + MEM_BOT [label="⊥:mem"]; + MEM_TOP -> MEM_BOT; + } + + subgraph cluster_tuple { + label="Tuple\ncollection of types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffc0cb"]; + TUPLE_TOP [label="⊤:tuple"]; + TUPLE_BOT [label="⊥:tuple"]; + // Each black element stands for a recursively nested type lattice. + TUPLE1 [shape=plain, label=< + + + +
tuple
type
+ >]; + TUPLE2 [shape=plain, label=< + + + + +
tuple
type
type
+ >]; + TUPLEN [shape=plain, label=< + + + + + +
tuple
type
…
type
+ >]; + {rank=same; TUPLE1; TUPLE2; TUPLEN;} + TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; + TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; + {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; + } + + TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; + {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; + {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} + {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} + XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; +} diff --git a/chapter10/docs/lattice.svg b/chapter10/docs/lattice.svg new file mode 100644 index 00000000..006ab762 --- /dev/null +++ b/chapter10/docs/lattice.svg @@ -0,0 +1,593 @@ + + + + + + +Lattice + + +cluster_control + +Control + + +cluster_integer + +Integer + + +cluster_pointer + +Pointer = nullability × struct type + + +cluster_struct + +Struct +named collection of named scalar types + + +cluster_memory + +Memory + + +cluster_tuple + +Tuple +collection of types + + + +TOP + +⊤ + + + +XCTRL + +⊤:ctrl + + + +TOP->XCTRL + + + + + +INT_TOP + +⊤:int + + + +TOP->INT_TOP + + + + + +PTR_TOP + +⊤:ptr +[*⊤:struct] + + + +TOP->PTR_TOP + + + + + +STRUCT_TOP + +⊤:struct +[struct top] + + + +TOP->STRUCT_TOP + + + + + +MEM_TOP + +⊤:mem + + + +TOP->MEM_TOP + + + + + +TUPLE_TOP + +⊤:tuple + + + +TOP->TUPLE_TOP + + + + + +BOT + +⊥ + + + +CTRL + +⊥:ctrl + + + +XCTRL->CTRL + + + + + + +CTRL->BOT + + + + + +INT_NEG + +… −2, −1 + + + +INT_TOP->INT_NEG + + + + + +INT_ZERO + +0 + + + +INT_TOP->INT_ZERO + + + + + +INT_POS + +1, 2, … + + + +INT_TOP->INT_POS + + + + + + +INT_BOT + +⊥:int + + + +INT_BOT->BOT + + + + + +INT_NEG->INT_BOT + + + + + + +INT_ZERO->INT_BOT + + + + + + +INT_POS->INT_BOT + + + + + +PTR_NULL + +null + + + +PTR_TOP->PTR_NULL + + + + + +PTR_PERSON + +*Person + + + +PTR_TOP->PTR_PERSON + + + + + +PTR_S1 + +*s1 + + + +PTR_TOP->PTR_S1 + + + + + +PTR_MORE + +*s2, … + + + +PTR_TOP->PTR_MORE + + + + + + +PTR_BOT + +⊥:ptr +[*⊥:struct?] + + + +PTR_BOT->BOT + + + + + +PTR_PERSON_NIL + +*Person? + + + +PTR_NULL->PTR_PERSON_NIL + + + + + +PTR_S1_NIL + +*s1? + + + +PTR_NULL->PTR_S1_NIL + + + + + +PTR_MORE_NIL + +*s2?, … + + + +PTR_NULL->PTR_MORE_NIL + + + + + +PTR_ANY + +*⊥:struct +[ptr any struct] + + + +PTR_ANY->PTR_BOT + + + + + + +PTR_PERSON->PTR_ANY + + + + + + +PTR_PERSON->PTR_PERSON_NIL + + + + + +PTR_S1->PTR_ANY + + + + + + +PTR_S1->PTR_S1_NIL + + + + + + +PTR_MORE->PTR_ANY + + + + + +PTR_MORE->PTR_MORE_NIL + + + + + +PTR_PERSON_NIL->PTR_BOT + + + + + + +PTR_S1_NIL->PTR_BOT + + + + + + +PTR_MORE_NIL->PTR_BOT + + + + + +STRUCT_PERSON + +Person +age: int + + + +STRUCT_TOP->STRUCT_PERSON + + + + + +STRUCT_S1 + +s1 +x: int + + + +STRUCT_TOP->STRUCT_S1 + + + + + +STRUCT_S2 + +s2 +x: int, y: int + + + +STRUCT_TOP->STRUCT_S2 + + + + + +STRUCT_MORE + +… + + + +STRUCT_TOP->STRUCT_MORE + + + + + + +STRUCT_BOT + +⊥:struct +[struct bottom] + + + +STRUCT_BOT->BOT + + + + + +STRUCT_PERSON->STRUCT_BOT + + + + + + +STRUCT_S1->STRUCT_BOT + + + + + + +STRUCT_S2->STRUCT_BOT + + + + + + +STRUCT_MORE->STRUCT_BOT + + + + + +MEM_BOT + +⊥:mem + + + +MEM_TOP->MEM_BOT + + + + + + +MEM_BOT->BOT + + + + + +TUPLE1 + + + +tuple + + +type + + + +TUPLE_TOP->TUPLE1 + + + + + +TUPLE2 + + + +tuple + + +type + + +type + + + +TUPLE_TOP->TUPLE2 + + + + + +TUPLEN + + + +tuple + + +type + + +… + + +type + + + +TUPLE_TOP->TUPLEN + + + + + +TUPLE_BOT + +⊥:tuple + + + +TUPLE_BOT->BOT + + + + + +TUPLE1->TUPLE_BOT + + + + + + +TUPLE2->TUPLE_BOT + + + + + + +TUPLEN->TUPLE_BOT + + + + + diff --git a/chapter10/docs/load-after-store-after.json b/chapter10/docs/load-after-store-after.json new file mode 100644 index 00000000..d27efe5b --- /dev/null +++ b/chapter10/docs/load-after-store-after.json @@ -0,0 +1,19 @@ +{ + "ver": 1, + "comp": "load-after-store-after", + "step": 1, + "roots": [10], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Box", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 4, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/load-after-store-after.svg b/chapter10/docs/load-after-store-after.svg new file mode 100644 index 00000000..78867d38 --- /dev/null +++ b/chapter10/docs/load-after-store-after.svg @@ -0,0 +1,97 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #4 · DATA#10[1] → #8 · MEM#10[2] → #4 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Box  #5#5 new Box +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.x  #8#8 Store p.x +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #4 DATA0123Return  #10#10 Return +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #4 DATA012 diff --git a/chapter10/docs/load-after-store-before.json b/chapter10/docs/load-after-store-before.json new file mode 100644 index 00000000..ba4e2e0b --- /dev/null +++ b/chapter10/docs/load-after-store-before.json @@ -0,0 +1,20 @@ +{ + "ver": 1, + "comp": "load-after-store-before", + "step": 1, + "roots": [10], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Box", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter10/docs/load-after-store-before.svg b/chapter10/docs/load-after-store-before.svg new file mode 100644 index 00000000..497c915d --- /dev/null +++ b/chapter10/docs/load-after-store-before.svg @@ -0,0 +1,103 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #4 · DATA#9[1] → #8 · MEM#9[2] → #5 · DATA#10[1] → #8 · MEM#10[2] → #9 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Box  #5#5 new Box +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7#7 Store p.x +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.x  #8#8 Store p.x +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #4 DATA0123Load p.x  #9#9 Load p.x +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → — DATA0123Return  #10#10 Return +Type not computed + +[0] → — CTRL +[1] → #8 MEM +[2] → #9 DATA012 diff --git a/chapter10/docs/type-control.svg b/chapter10/docs/type-control.svg new file mode 100644 index 00000000..d4b79c53 --- /dev/null +++ b/chapter10/docs/type-control.svg @@ -0,0 +1,5 @@ + + Control + + Control + diff --git a/chapter10/docs/type-integer.svg b/chapter10/docs/type-integer.svg new file mode 100644 index 00000000..97d03372 --- /dev/null +++ b/chapter10/docs/type-integer.svg @@ -0,0 +1,5 @@ + + Integer + + Integer + diff --git a/chapter10/docs/type-memory.svg b/chapter10/docs/type-memory.svg new file mode 100644 index 00000000..16b5121f --- /dev/null +++ b/chapter10/docs/type-memory.svg @@ -0,0 +1,5 @@ + + Memory + + Memory + diff --git a/chapter10/docs/type-pointer.svg b/chapter10/docs/type-pointer.svg new file mode 100644 index 00000000..7604492e --- /dev/null +++ b/chapter10/docs/type-pointer.svg @@ -0,0 +1,5 @@ + + Pointer + + Pointer + diff --git a/chapter10/docs/type-struct.svg b/chapter10/docs/type-struct.svg new file mode 100644 index 00000000..97a16562 --- /dev/null +++ b/chapter10/docs/type-struct.svg @@ -0,0 +1,5 @@ + + Struct + + Struct + diff --git a/chapter10/docs/type-tuple.svg b/chapter10/docs/type-tuple.svg new file mode 100644 index 00000000..691478ac --- /dev/null +++ b/chapter10/docs/type-tuple.svg @@ -0,0 +1,5 @@ + + Tuple + + Tuple + diff --git a/chapter10/pom.xml b/chapter10/pom.xml new file mode 100644 index 00000000..6041c8f8 --- /dev/null +++ b/chapter10/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.seaofnodes + simple + 1.0 + + chapter10 + jar + Chapter 10 + + ${project.basedir}/../graph + ${project.basedir}/../print + + diff --git a/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java new file mode 100644 index 00000000..621bc2d6 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.print.IRAdapter; +import com.seaofnodes.simple.node.*; + +/** This chapter's read-only IR facts and the familiar debugger entry points. */ +public final class IRPrinter extends IRAdapter { + private static final com.seaofnodes.print.IRPrinter PRINT = + new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); + @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } + @Override public int inputColumns() { return 3; } + @Override public Kind kind(Node n) { + if( n instanceof StartNode ) return Kind.START; + if( n instanceof StopNode ) return Kind.STOP; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + if( n instanceof ReturnNode ) return Kind.RETURN; + if( n instanceof ConstantNode ) return Kind.CONSTANT; + if( n instanceof ProjNode ) return n.isCFG() ? Kind.CPROJ : Kind.PROJ; + if( n.isCFG() ) return Kind.CTRL; + if( n instanceof MultiNode ) return Kind.MULTI; + return Kind.DATA; + } + @Override public int index(Node n) { + if( n instanceof ProjNode p ) return p._idx; + return n._nid; + } + + public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java new file mode 100644 index 00000000..7075d526 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -0,0 +1,208 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Random; + +/** + * The IterOptim runs after parsing. It iterates the peepholes to a fixed point + * so that no more peepholes apply. This should be linear because peepholes rarely + * (never?) increase code size. The graph should monotonically reduce in some + * dimension, which is usually size. It might also reduce in e.g. number of + * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper + * ones. + * + * The theoretical overall worklist is mindless just grabbing the next thing and + * doing it. If the graph changes, put the neighbors on the worklist. Lather, + * Rinse, Repeat until the worklist runs dry. + * + * The main issues we have to deal with: + * + *
    + *
  • Nodes have uses; replacing some set of Nodes with another requires more graph + * reworking. Not rocket science, but it can be fiddly. Its helpful to have a + * small set of graph munging utilities, and the strong invariant that the graph + * is stable and correct between peepholes. In our case `Node.subsume` does + * most of the munging, building on our prior stable Node utilities.
  • + * + *
  • Changing a Node also changes the graph "neighborhood". The neigbors need to + * be checked to see if THEY can also peephole, and so on. After any peephole + * or graph update we put a Nodes uses and defs on the worklist.
  • + * + *
  • Our strong invariant is that for all Nodes, either they are on the worklist + * OR no peephole applies. This invariant is easy to check, although expensive. + * Basically the normal "iterate peepholes to a fixed point" is linear, and this + * check is linear at each peephole step... so quadratic overall. Its a useful + * assert, but one we can disable once the overall algorithm is stable - and + * then turn it back on again when some new set of peepholes is misbehaving. + * The code for this is turned on in `IterOptim.iterate` as `assert + * progressOnList(stop);`
  • + *
+ */ +public abstract class IterPeeps { + + private static final WorkList WORK = new WorkList<>(); + + public static N add( N n ) { + return (N)WORK.push(n); + } + + public static void addAll( ArrayList ary ) { + WORK.addAll(ary); + } + + /** + * Iterate peepholes to a fixed point + */ + public static StopNode iterate(StopNode stop) { + assert progressOnList(stop); + int cnt=0; + + Node n; + 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() ) { + 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 + if( x != n || !(x instanceof ConstantNode) ) { + // All outputs of n (changing node) not x (prior existing node). + for( Node z : n._outputs ) WORK.push(z); + // Everybody gets a free "go again" in case they didn't get + // made in their final form. + WORK.push(x); + // If the result is not self, revisit all inputs (because + // there's a new user), and replace in the graph. + if( x != n ) { + for( Node z : n. _inputs ) WORK.push(z); + n.subsume(x); + } + } + // If there are distant neighbors, move to worklist + n.moveDepsToWorklist(); + 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); + } + + return stop; + } + + // Visit ALL nodes and confirm the invariant: + // Either you are on the WORK worklist OR running `iter()` makes no progress. + + // This invariant ensures that no progress is missed, i.e., when the + // worklist is empty we have indeed done all that can be done. To help + // with debugging, the {@code assert} is broken out in a place where it is easy to + // stop if a change is found. + + // Also, the normal usage of `iter()` may attempt peepholes with distance + // neighbors and these should fail, but will then try to add dependencies + // {@link #Node.addDep} which is a side effect in an assert. The {@link + // #midAssert} is used to stop this side effect. + private static boolean MID_ASSERT; + public static boolean midAssert() { return MID_ASSERT; } + private static boolean progressOnList(Node stop) { + MID_ASSERT = true; + int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; + Node changed = stop.walk( n -> { + if( WORK.on(n) ) return null; + Node m = n.peepholeOpt(); + if( m==null ) return null; + System.err.println("BREAK HERE FOR BUG"); + return m; + }); + Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop; + MID_ASSERT = false; + return changed==null; + } + + public static void reset() { + WORK.clear(); + } + + /** + * Classic WorkList, with a fast add/remove, dup removal, random pull. + * The Node's nid is used to check membership in the worklist. + */ + public static class WorkList { + + private Node[] _es; + private int _len; + private final BitSet _on; // Bit set if Node._nid is on WorkList + private final Random _R; // For randomizing pull from the WorkList + private final long _seed; + + /* Useful stat - how many nodes are processed in the post parse iterative opt */ + private long _totalWork = 0; + + WorkList() { this(123); } + WorkList(long seed) { + _es = new Node[1]; + _len=0; + _on = new BitSet(); + _seed = seed; + _R = new Random(); + _R.setSeed(_seed); + } + + /** + * Pushes a Node on the WorkList, ensuring no duplicates + * If Node is null it will not be added. + */ + public E push( E x ) { + if( x==null ) return null; + int idx = x._nid; + if( !_on.get(idx) ) { + _on.set(idx); + if( _len==_es.length ) + _es = Arrays.copyOf(_es,_len<<1); + _es[_len++] = x; + _totalWork++; + } + return x; + } + + public void addAll( ArrayList ary ) { + for( E n : ary ) + push(n); + } + + /** + * True if Node is on the WorkList + */ + boolean on( E x ) { return _on.get(x._nid); } + + /** + * Removes a random Node from the WorkList; null if WorkList is empty + */ + E pop() { + if( _len == 0 ) return null; + int idx = _R.nextInt(_len); + E x = (E)_es[idx]; + _es[idx] = _es[--_len]; // Compress array + _on.clear(x._nid); + return x; + } + + public void clear() { + _len = 0; + _on.clear(); + _R.setSeed(_seed); + _totalWork = 0; + } + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java new file mode 100644 index 00000000..7734f4df --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java @@ -0,0 +1,827 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.graph.GraphObserver; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +import java.util.*; + +/** + * The Parser converts a Simple source program to the Sea of Nodes intermediate + * representation directly in one pass. There is no intermediate Abstract + * Syntax Tree structure. + *

+ * This is a simple recursive descent parser. All lexical analysis is done here as well. + */ +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 + * through the entire parser and optimizer. + *

+ * To make the compiler multithreaded, this field will have to move into a TLS. + */ + public static StartNode START; + + public StopNode STOP; + + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. + private final Lexer _lexer; + + /** + * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time + * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how + * we handle branching. See {@link #parseIf()}. + *

+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds + * variable names to Nodes. The top of this stack represents current scope. + *

+ * We keep a list of all ScopeNodes so that we can show them in graphs. + * @see #parseIf() + * @see #_xScopes + */ + public ScopeNode _scope; + + /** + * List of keywords disallowed as identifiers + */ + private static final HashSet KEYWORDS = new HashSet<>(){{ + add("break"); + add("continue"); + add("else"); + add("false"); + add("if"); + add("int"); + add("new"); + add("null"); + add("return"); + add("struct"); + add("true"); + add("while"); + }}; + + + /** + * We clone ScopeNodes when control flows branch; it is useful to have + * a list of all active ScopeNodes for purposes of visualization of the SoN graph + */ + public final Stack _xScopes = new Stack<>(); + + ScopeNode _continueScope; + ScopeNode _breakScope; + + // Mapping from a Struct name to a Struct. + public static Map OBJS = new HashMap<>(); + + public Parser(String source, TypeInteger arg) { + PARSER = this; + Node.reset(); + IterPeeps.reset(); + OBJS.clear(); + _lexer = new Lexer(source); + _scope = new ScopeNode(); + _continueScope = _breakScope = null; + START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); + STOP = new StopNode(source); + } + + public Parser(String source) { + this(source, TypeInteger.BOT); + } + + @Override + public String toString() { return _lexer.toString(); } + + String src() { return new String( _lexer._input ); } + + // Debugging utility to find a Node by index + public static Node find(int nid) { return START.find(nid); } + + private Node ctrl() { return _scope.ctrl(); } + + private Node ctrl(Node n) { return _scope.ctrl(n); } + + public StopNode parse() { + _xScopes.push(_scope); + // Enter a new scope for the initial control and arguments + _scope.push(); + _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); + parseBlock(); + _scope.pop(); + _xScopes.pop(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); + STOP.peephole(); + return STOP; + } + + /** + * Parses a block + * + *

+     *     '{' statements '}'
+     * 
+ * Does not parse the opening or closing '{}' + * @return a {@link Node} or {@code null} + */ + private Node parseBlock() { + // Enter a new scope + _scope.push(); + while (!peek('}') && !_lexer.isEOF()) + parseStatement(); + // Exit scope + _scope.pop(); + return null; + } + + /** + * Parses a statement + * + *
+     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+     * 
+ * @return a {@link Node} or {@code null} + */ + private Node parseStatement() { + if( false ) return null; + else if (matchx("return") ) return parseReturn(); + else if (matchx("int") ) return parseDecl(TypeInteger.BOT); + else if (match ("{") ) return require(parseBlock(),"}"); + else if (matchx("if") ) return parseIf(); + else if (matchx("while") ) return parseWhile(); + else if (matchx("break") ) return parseBreak(); + else if (matchx("continue")) return parseContinue(); + else if (matchx("struct") ) return parseStruct(); + else if (matchx(";") ) return null; // Empty statement + // declarations of vars with struct type are handled in parseExpressionStatement due + // to ambiguity + else return parseExpressionStatement(); + } + + /** + * Parse a struct field. + *
+     *     int IDENTIFIER ;
+     * 
+ */ + private Field parseField(String sname) { + if( matchx("int") ) // Currently only parsing "int" type fields + return require(Field.make(requireId().intern(),TypeInteger.BOT),";"); + throw errorSyntax("A field type is expected, only type 'int' is supported at present"); + } + + /** + * Parse a struct declaration, and return the following statement. + * Only allowed in top level scope. + * Structs cannot be redefined. + * + * @return The statement following the struct + */ + private Node parseStruct() { + if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + String typeName = requireId(); + if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + ArrayList fields = new ArrayList<>(); + require("{"); + while (!peek('}') && !_lexer.isEOF()) { + Field field = parseField(typeName); + if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); + fields.add(field); + } + require("}"); + // Build and install the TypeStruct + TypeStruct ts = TypeStruct.make(typeName, fields); + OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names + return parseStatement(); + } + + /** + * Parses a while statement + * + *
+     *     while ( expression ) statement
+     * 
+ * @return a {@link Node}, never {@code null} + */ + private Node parseWhile() { + + var savedContinueScope = _continueScope; + var savedBreakScope = _breakScope; + + require("("); + + // Loop region has two control inputs, the first is the entry + // point, and second is back edge that is set after loop is parsed + // (see end_loop() call below). Note that the absence of back edge is + // used as an indicator to switch off peepholes of the region and + // associated phis; see {@code inProgress()}. + + ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here + + // At loop head, we clone the current Scope (this includes all + // names in every nesting level within the Scope). + // We create phis eagerly for all the names we find, see dup(). + + // Save the current scope as the loop head + ScopeNode head = _scope.keep(); + // Clone the head Scope to create a new Scope for the body. + // Create phis eagerly as part of cloning + _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis + + // Parse predicate + var pred = require(parseExpression(), ")"); + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); + + // Clone the body Scope to create the break/exit Scope which accounts for any + // side effects in the predicate. The break/exit Scope will be the final + // scope after the loop, and its control input is the False branch of + // the loop predicate. Note that body Scope is still our current scope. + ctrl(ifF); + _xScopes.push(_breakScope = _scope.dup()); + + // No continues yet + _continueScope = null; + + // Parse the true side, which corresponds to loop body + // Our current scope is the body Scope + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + parseStatement(); // Parse loop body + + // Merge the loop bottom into other continue statements + if (_continueScope != null) { + _continueScope = jumpTo(_continueScope); + _scope.kill(); + _scope = _continueScope; + } + + // The true branch loops back, so whatever is current _scope.ctrl gets + // added to head loop as input. endLoop() updates the head scope, and + // goes through all the phis that were created earlier. For each phi, + // it sets the second input to the corresponding input from the back + // edge. If the phi is redundant, it is replaced by its sole input. + var exit = _breakScope; + head.endLoop(_scope, exit); + head.unkeep().kill(); + + _xScopes.pop(); // Cleanup + _xScopes.pop(); // Cleanup + + _continueScope = savedContinueScope; + _breakScope = savedBreakScope; + + // At exit the false control is the current control, and + // the scope is the exit scope after the exit test. + return _scope = exit; + } + + private ScopeNode jumpTo(ScopeNode toScope) { + ScopeNode cur = _scope.dup(); + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope + // Prune nested lexical scopes that have depth > than the loop head + // We use _breakScope as a proxy for the loop head scope to obtain the depth + while( cur._scopes.size() > _breakScope._scopes.size() ) + cur.pop(); + // If this is a continue then first time the target is null + // So we just use the pruned current scope as the base for the + // "continue" + if (toScope == null) + return cur; + // toScope is either the break scope, or a scope that was created here + assert toScope._scopes.size() <= _breakScope._scopes.size(); + toScope.ctrl(toScope.mergeScopes(cur)); + return toScope; + } + + private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } + + private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); } + private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } + + /** + * Parses a statement + * + *
+     *     if ( expression ) statement [else statement]
+     * 
+ * @return a {@link Node}, never {@code null} + */ + private Node parseIf() { + require("("); + // Parse predicate + var pred = require(parseExpression(), ")").keep(); + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + // In if true branch, the ifT proj node becomes the ctrl + // But first clone the scope and set it as current + int ndefs = _scope.nIns(); + ScopeNode fScope = _scope.dup(); // Duplicate current scope + fScope.ctrl(ifF); // The pending arm already starts at False + _xScopes.push(fScope); // For graph visualization we need all scopes + + // Parse the true side + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.upcast(ifT,pred,false); // Up-cast predicate + parseStatement(); // Parse true-side + ScopeNode tScope = _scope; + + // Parse the false side + _scope = fScope; // Restore scope, then parse else block if any + ifF.unkeep(); // fScope already owns the false control + if (matchx("else")) { + _scope.upcast(ifF,pred,true); // Up-cast predicate + parseStatement(); + fScope = _scope; + } + pred.unkeep(); + + if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) + throw error("Cannot define a new name on one arm of an if"); + + // Merge results + _scope = tScope; + _xScopes.pop(); // Discard pushed from graph display + + return ctrl(tScope.mergeScopes(fScope)); + } + + + /** + * Parses a return statement; "return" already parsed. + * The $ctrl edge is killed. + * + *
+     *     'return' expr ;
+     * 
+ * @return an expression {@link Node}, never {@code null} + */ + private Node parseReturn() { + var expr = require(parseExpression(), ";"); + Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control + return ret; + } + + + + /** + * Parses an expression statement or a declaration statement where type is a struct + * + *
+     *      name;         // Error
+     * type name;         // Define name with default initial value
+     * type name = expr;  // Define name with given   initial value
+     *      name = expr;  // Reassign existing
+     *             expr   // Something else
+     * 
+ * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpressionStatement() { + int old = _lexer._position; + Type t = type(); + String name = requireId(); + Node expr; + if( match(";") ) { // Assign a default value + // No type and no expr is an error + if( t==null ) throw errorSyntax("expression"); + expr = new ConstantNode(t.makeInit()).peephole(); + } else if( match("=") ) { // Assign "= expr;" + expr = require(parseExpression(), ";"); + } else { // Neither, so just a normal expression parse + _lexer._position = old; + return require(parseExpression(),";"); + } + + // Defining a new variable vs updating an old one + if( t != null ) { + if( _scope.define(name,t,expr) == null ) + throw error("Redefining name '" + name + "'"); + } else { + Node n = _scope.lookup(name); + t = _scope.lookupDeclaredType(name); + if( n==null ) + throw error("Undefined name '" + name + "'"); + _scope.update(name,expr); + } + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + return expr; + } + + // Parse a type-or-null + private Type type() { + int old = _lexer._position; + String tname = _lexer.matchId(); + if( tname==null ) return null; + if( tname.equals("int") ) return TypeInteger.BOT; + TypeStruct obj = OBJS.get(tname); + if( obj != null ) + return TypeMemPtr.make(obj,match("?")); + // Not a type; unwind the parse + _lexer._position = old; + return null; + } + + /** + * Parses a declStatement + * + *
+     *     type name = expression ';'
+     * 
+ * @return an expression {@link Node} + */ + private Node parseDecl(Type t) { + var name = requireId(); + var expr = match(";") + // Assign a null value + ? new ConstantNode(t.makeInit()).peephole() + // Assign "= expr;" + : require(require("=").parseExpression(), ";"); + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + if( _scope.define(name,t,expr) == null ) + throw error("Redefining name '" + name + "'"); + return expr; + } + + + /** + * Parse an expression of the form: + * + *
+     *     expr : compareExpr
+     * 
+ * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpression() { return parseComparison(); } + + /** + * Parse an expression of the form: + * + *
+     *     expr : additiveExpr op additiveExpr
+     * 
+ * @return an comparator expression {@link Node}, never {@code null} + */ + private Node parseComparison() { + var lhs = parseAddition(); + while( true ) { + int idx=0; boolean negate=false; + // Test for any local nodes made, and "keep" lhs during peepholes + if( false ) ; + else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } + else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } + else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } + else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } + else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } + else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } + else break; + // Peepholes can fire, but lhs is already "hooked", kept alive + lhs.setDef(idx,parseAddition()); + lhs = lhs.peephole(); + if( negate ) // Extra negate for != + lhs = new NotNode(lhs).peephole(); + } + return lhs; + } + + /** + * Parse an additive expression + * + *
+     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+     * 
+ * @return an add expression {@link Node}, never {@code null} + */ + private Node parseAddition() { + Node lhs = parseMultiplication(); + while( true ) { + if( false ) ; + else if( match("+") ) lhs = new AddNode(lhs,null); + else if( match("-") ) lhs = new SubNode(lhs,null); + else break; + lhs.setDef(2,parseMultiplication()); + lhs = lhs.peephole(); + } + return lhs; + } + + /** + * Parse an multiplicativeExpr expression + * + *
+     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+     * 
+ * @return a multiply expression {@link Node}, never {@code null} + */ + private Node parseMultiplication() { + var lhs = parseUnary(); + while( true ) { + if( false ) ; + else if( match("*") ) lhs = new MulNode(lhs,null); + else if( match("/") ) lhs = new DivNode(lhs,null); + else break; + lhs.setDef(2,parseUnary()); + lhs = lhs.peephole(); + } + return lhs; + } + + /** + * Parse a unary minus expression. + * + *
+     *     unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
+     * 
+ * @return a unary expression {@link Node}, never {@code null} + */ + private Node parseUnary() { + if (match("-")) return new MinusNode(parseUnary()).peephole(); + if (match("!")) return new NotNode(parseUnary()).peephole(); + return parsePostfix(parsePrimary()); + } + + /** + * Parse a primary expression: + * + *
+     *     primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')'
+     * 
+ * @return a primary {@link Node}, never {@code null} + */ + private Node parsePrimary() { + if( _lexer.isNumber() ) return parseIntegerLiteral(); + if( match("(") ) return require(parseExpression(), ")"); + if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); + if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); + if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); + if( matchx("new") ) { + String structName = requireId(); + TypeStruct obj = OBJS.get(structName); + if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'"); + return newStruct(obj); + } + String name = _lexer.matchId(); + if( name == null) throw errorSyntax("an identifier or expression"); + Node n = _scope.lookup(name); + if( n!=null ) return n; + throw error("Undefined name '" + name + "'"); + } + + /** + * Return a NewNode but also generate instructions to initialize it. + */ + private Node newStruct( TypeStruct obj ) { + Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); + Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); + for( Field field : obj._fields ) { + mem(new StoreNode(field._fname, mem(), n, initValue).peephole()); + } + return n.unkeep(); + } + + // Memory is one hidden SSA variable, including across branches and loops. + private Node mem() { return _scope.lookup("$mem"); } + private Node mem(Node n) { return _scope.update("$mem",n); } + + /** + * Parse postfix expression. For now this is just a field + * expression, but in future could be array index too. + * + *
+     *     expr ('.' IDENTIFIER)* [ = expr ]
+     * 
+ */ + private Node parsePostfix(Node expr) { + if( !match(".") ) return expr; + + if( !(expr._type instanceof TypeMemPtr ptr) ) + throw error("Expected struct reference but got " + expr._type.str()); + + String name = requireId().intern(); + int idx = ptr._obj==null ? -1 : ptr._obj.find(name); + if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + + if( match("=") ) { + // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment + if( peek('=') ) _lexer._position--; + else { + Node val = parseExpression(); + mem(new StoreNode(name, mem(), expr, val).peephole()); + return expr; // "obj.a = expr" returns the expression while updating memory + } + } + + Type declaredType = ptr._obj._fields[idx]._type; + return parsePostfix(new LoadNode(name, declaredType, mem(), expr).peephole()); + } + + /** + * Parse integer literal + * + *
+     *     integerLiteral: [1-9][0-9]* | [0]
+     * 
+ */ + private ConstantNode parseIntegerLiteral() { + return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); + } + + ////////////////////////////////// + // Utilities for lexical analysis + + // Return true and skip if "syntax" is next in the stream. + private boolean match (String syntax) { return _lexer.match (syntax); } + // Match must be "exact", not be followed by more id letters + private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + // Return true and do NOT skip if 'ch' is next + private boolean peek(char ch) { return _lexer.peek(ch); } + private boolean peekIsId() { return _lexer.peekIsId(); } + + // Require and return an identifier + private String requireId() { + String id = _lexer.matchId(); + if (id != null && !KEYWORDS.contains(id) ) return id; + throw error("Expected an identifier, found '"+id+"'"); + } + + + + // Require an exact match + private Parser require(String syntax) { require(null, syntax); return this; } + private N require(N n, String syntax) { + if (match(syntax)) return n; + throw errorSyntax(syntax); + } + + RuntimeException errorSyntax(String syntax) { + return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); + } + + public static RuntimeException error( String errorMessage ) { + return new RuntimeException(errorMessage); + } + + //////////////////////////////////// + // Lexer components + + private static class Lexer { + + // Input buffer; an array of text bytes read from a file or a string + private final byte[] _input; + // Tracks current position in input buffer + private int _position = 0; + + /** + * Record the source text for lexing + */ + public Lexer(String source) { + this(source.getBytes()); + } + + /** + * Direct from disk file source + */ + public Lexer(byte[] buf) { + _input = buf; + } + + // Very handy in the debugger, shows the unparsed program + @Override + public String toString() { + return new String(_input, _position, _input.length - _position); + } + + // True if at EOF + private boolean isEOF() { + return _position >= _input.length; + } + + // Peek next character, or report EOF + private char peek() { + return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate + : (char) _input[_position]; + } + + private char nextChar() { + char ch = peek(); + _position++; + return ch; + } + + // True if a white space + private boolean isWhiteSpace() { + return peek() <= ' '; // Includes all the use space, tab, newline, CR + } + + /** + * Return the next non-white-space character + */ + private void skipWhiteSpace() { + while (isWhiteSpace()) _position++; + } + + + // Return true, if we find "syntax" after skipping white space; also + // then advance the cursor past syntax. + // Return false otherwise, and do not advance the cursor. + boolean match(String syntax) { + skipWhiteSpace(); + int len = syntax.length(); + if (_position + len > _input.length) return false; + for (int i = 0; i < len; i++) + if ((char) _input[_position + i] != syntax.charAt(i)) + return false; + _position += len; + return true; + } + + // Match must be exact and not followed by more ID characters. + // Prevents identifier "ifxy" from matching an "if" statement. + boolean matchx(String syntax) { + if( !match(syntax) ) return false; + if( !isIdLetter(peek()) ) return true; + _position -= syntax.length(); + return false; + } + + private boolean peek(char ch) { + skipWhiteSpace(); + return peek()==ch; + } + + boolean peekIsId() { + skipWhiteSpace(); + return isIdStart(peek()); + } + + // Return an identifier or null + String matchId() { + return peekIsId() ? parseId() : null; + } + + // Used for errors + String getAnyNextToken() { + if (isEOF()) return ""; + if (isIdStart(peek())) return parseId(); + if (isNumber(peek())) return parseNumberString(); + if (isPunctuation(peek())) return parsePunctuation(); + return String.valueOf(peek()); + } + + boolean isNumber() {return isNumber(peek());} + boolean isNumber(char ch) {return Character.isDigit(ch);} + + private Type parseNumber() { + String snum = parseNumberString(); + if (snum.length() > 1 && snum.charAt(0) == '0') + throw error("Syntax error: integer values cannot start with '0'"); + return TypeInteger.constant(Long.parseLong(snum)); + } + private String parseNumberString() { + int start = _position; + while (isNumber(nextChar())) ; + return new String(_input, start, --_position - start); + } + + // First letter of an identifier + private boolean isIdStart(char ch) { + return Character.isAlphabetic(ch) || ch == '_'; + } + + // All characters of an identifier, e.g. "_x123" + private boolean isIdLetter(char ch) { + return Character.isLetterOrDigit(ch) || ch == '_'; + } + + private String parseId() { + int start = _position; + while (isIdLetter(nextChar())) ; + return new String(_input, start, --_position - start); + } + + private boolean isPunctuation(char ch) { + return "=;[]<>(){}+-/*!".indexOf(ch) != -1; + } + + private String parsePunctuation() { + int start = _position; + return new String(_input, start, 1); + } + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java new file mode 100644 index 00000000..4f2af769 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java @@ -0,0 +1,65 @@ +package com.seaofnodes.simple; + +import java.util.ArrayList; + +public class Utils { + public static RuntimeException TODO() { return TODO("Not yet implemented"); } + public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } + + /** + * Fast, constant-time, element removal. Does not preserve order + * + * @param array ArrayList to modify + * @param i element to be removed + * @return element removed + */ + public static E del(ArrayList array, int i) { + if ( i >= 0 && i < array.size() ) { + E tmp = array.get(i); + E last = array.removeLast(); + if (i < array.size()) array.set(i, last); + return tmp; + } + return null; + } + + /** + * Search a list for an element by reference + * + * @param ary List to search in + * @param x Object to be searched + * @return >= 0 on success, -1 on failure + */ + public static int find( ArrayList ary, E x ) { + for( int i=0; i= 0 on success, -1 on failure + */ + public static int find( E[] ary, E x ) { + for( int i=0; i>>n); } + + // Fold a 64bit hash into 32 bits + public static int fold( long x ) { return (int)((x>>32) ^ x); } + + public static boolean eq(String s0, String s1) { + if( s0==s1 ) return true; + assert s0==s0.intern(); + assert s1==s1.intern(); + return false; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java new file mode 100644 index 00000000..e235d288 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -0,0 +1,171 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.*; + + +public class AddNode extends Node { + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Add"; } + + @Override protected String format() { return "(%1+%2)"; } + + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()+i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize () { + Node lhs = in(1); + Node rhs = in(2); + Type t2 = rhs._type; + + // Add of 0. We do not check for (0+x) because this will already + // canonicalize to (x+0) + if( t2 instanceof TypeInteger i && i.value()==0 ) + return lhs; + + // Add of same to a multiply by 2 + if( lhs==rhs ) + return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); + + // Goal: a left-spine set of adds, with constants on the rhs (which then fold). + + // Move non-adds to RHS + if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) + return swap12(); + + // x+(-y) becomes x-y + if( rhs instanceof MinusNode minus ) + return new SubNode(lhs,minus.in(1)); + + // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) + + // Do we have x + (y + z) ? + // Swap to (x + y) + z + // Rotate (add add add) to remove the add on RHS + if( rhs instanceof AddNode add ) + return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + + // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) + if( !(lhs instanceof AddNode) ) + // Rotate; look for (add (phi cons) con/(phi cons)) + return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); + + // Now we only see (add add non) + + // Dead data cycle; comes about from dead infinite loops. Do nothing, + // the loop will peep as dead after a bit. + if( lhs.in(1) == lhs ) + return null; + + // Do we have (x + con1) + con2? + // Replace with (x + (con1+con2) which then fold the constants + // lhs.in(2) is con1 here + // If lhs.in(2) is not a constant, we add ourselves as a dependency + // because if it later became a constant then we could make this + // transformation. + if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) + return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + + + // Do we have ((x + (phi cons)) + con) ? + // Do we have ((x + (phi cons)) + (phi cons)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + Node phicon = phiCon(this,true); + if( phicon!=null ) return phicon; + + // Now we sort along the spine via rotates, to gather similar things together. + + // Do we rotate (x + y) + z + // into (x + z) + y ? + if( spine_cmp(lhs.in(2),rhs,this) ) + return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); + + return null; + } + + // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or. + // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + static Node phiCon(Node op, boolean rotate) { + Node lhs = op.in(1); + Node rhs = op.in(2); + // LHS is either a Phi of constants, or another op with Phi of constants + PhiNode lphi = pcon(lhs,op); + if( rotate && lphi==null && lhs.nIns() > 2 ) { + // Only valid to rotate constants if both are same associative ops + if( lhs.getClass() != op.getClass() ) return null; + lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push + } + if( lphi==null ) return null; + + // RHS is a constant or a Phi of constants + if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) + return null; + + // If both are Phis, must be same Region + if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) + return null; + + // Note that this is the exact reverse of Phi pulling a common op down + // to reduce total op-count. We don't get in an endless push-up + // push-down peephole cycle because the constants all fold first. + Node[] ns = new Node[lphi.nIns()]; + ns[0] = lphi.in(0); + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + for( int i=1; i hi._nid; + } + + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java new file mode 100644 index 00000000..63452dba --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -0,0 +1,74 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; + + +abstract public class BoolNode extends Node { + + public BoolNode(Node lhs, Node rhs) { + super(null, lhs, rhs); + } + + abstract String op(); // String opcode name + + @Override + public String label() { return getClass().getSimpleName(); } + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.open().n(in(1)).p(op()).n(in(2)).close(); + } + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); + } + return TypeInteger.BOT; + } + + abstract boolean doOp(long lhs, long rhs); + + @Override + public Node idealize() { + // Compare of same + if( in(1)==in(2) ) + return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); + + // Equals pushes constant to the right; 5==X becomes X==5. + if( this instanceof EQ ) { + if( !(in(2) instanceof ConstantNode) ) { + // con==noncon becomes noncon==con + if( in(1) instanceof ConstantNode ) + return new EQ(in(2),in(1)); + // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high + else if( in(1)._nid > in(2)._nid ) + return new EQ(in(2),in(1)); + } + // Equals X==0 becomes a !X + if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) + return new NotNode(in(1)); + } + + // Do we have ((phi cons) cmp con) ? + // Do we have ((phi cons) cmp (phi cons)) ? + // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) + Node phicon = AddNode.phiCon(this,this instanceof EQ); + if( phicon!=null ) return phicon; + + return null; + } + + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } + public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java new file mode 100644 index 00000000..c1faf210 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.IterPeeps; + + +// Upcast (join) the input to a t. Used after guard test to lift an input. +// Can also be used to make a type-assertion if ctrl is null. +public class CastNode extends Node { + private final Type _t; + public CastNode(Type t, Node ctrl, Node in) { + super(ctrl, in); + _t = t; + setType(compute()); + } + + @Override public String label() { return "("+_t.str()+")"; } + + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p(label()).n(in(1)); + } + + @Override + public Type compute() { + return in(1)._type.join(_t); + } + + @Override + public Node idealize() { + return in(1)._type.isa(_t) ? in(1) : null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java new file mode 100644 index 00000000..426736ed --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; + + +/** + * A Constant node represents a constant value. At present, the only constants + * that we allow are integer literals; therefore Constants contain an integer + * value. As we add other types of constants, we will refactor how we represent + * Constants. + *

+ * Constants have no semantic inputs. However, we set Start as an input to + * Constants to enable a forward graph walk. This edge carries no semantic + * meaning, and it is present solely to allow visitation. + *

+ * The Constant's value is the value stored in it. + */ +public class ConstantNode extends Node { + final Type _con; + public ConstantNode( Type type ) { + super(Parser.START); + _con = type; + } + + @Override protected String repeatName() { + return _con==null || _con.toString().length()<=32 ? null : uniqueName(); + } + + @Override + public String label() { return "Con"; } + + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p(_con); + } + + @Override public boolean isMultiTail() { return true; } + + @Override + public Type compute() { return _con; } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { + ConstantNode con = (ConstantNode)n; // Contract + return _con==con._con; + } + + @Override + int hash() { return _con.hashCode(); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java new file mode 100644 index 00000000..d7244ace --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + + +public class DivNode extends Node { + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override protected String format() { return "(%1/%2)"; } + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return i1.value() == 0 + ? TypeInteger.ZERO + : TypeInteger.constant(i0.value()/i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + // Div of 1. + if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 ) + return in(1); + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java new file mode 100644 index 00000000..45f695a5 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -0,0 +1,58 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeTuple; + +public class IfNode extends MultiNode { + + public IfNode(Node ctrl, Node pred) { + super(ctrl, pred); + IterPeeps.add(this); // Because idoms are complex, just add it + } + + @Override + public String label() { return "If"; } + + @Override protected String format() { return "if( %1 )"; } + + @Override public boolean isCFG() { return true; } + @Override public boolean isMultiHead() { return true; } + + public Node ctrl() { return in(0); } + public Node pred() { return in(1); } + + @Override + public Type compute() { + // If the If node is not reachable then neither is any following Proj + if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) + return TypeTuple.IF_NEITHER; + Node pred = pred(); + Type t = pred._type; + // High types mean NEITHER side is reachable. + // Wait until the type falls to decide which way to go. + if( t == Type.TOP || t == TypeInteger.TOP ) + return TypeTuple.IF_NEITHER; + // If constant is 0 then false branch is reachable + // Else true branch is reachable + if (t instanceof TypeInteger ti && ti.isConstant()) + return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + + return TypeTuple.IF_BOTH; + } + + @Override + public Node idealize() { + // Hunt up the immediate dominator tree. If we find an identical if + // test on either the true or false branch, that side wins. + if( !pred()._type.isHighOrConst() ) + for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { + setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); + return this; + } + return null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java new file mode 100644 index 00000000..31c7699c --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -0,0 +1,90 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; + + +/** + * Load represents extracting a value from inside a memory object, + * in chapter 10 this means Struct fields. + */ +public class LoadNode extends MemOpNode { + + Type _declaredType; + /** + * Load a value from a ptr.field. + * + * @param name The field we are loading + * @param memSlice The whole-memory node - this is updated after a Store + * @param memPtr The ptr to the struct from where we load a field + */ + public LoadNode(String name, Type glb, Node memSlice, Node memPtr) { + super(name, memSlice, memPtr, null); + _declaredType = glb; + } + + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + } + + // A Store directly clobbers this memory. A Phi might lead to a clobber + // through a cycle; conservatively stop rather than searching beyond it. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode ) return true; + } + return false; + } + + @Override + public String label() { return "Load"; } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { return p.p(".").p(_name); } + + @Override + public Type compute() { + return _declaredType; + } + + @Override + public Node idealize() { + + // Simple Load-after-Store on same address. + if( mem() instanceof StoreNode st && + ptr() == st.ptr() && _name.equals(st._name) ) { // Must check same object + return st.val(); + } + + // Push a Load up through a Phi, as long as it collapses on at least + // one arm. If at a Loop, the backedge MUST collapse - else we risk + // spinning the same transform around the loop indefinitely. + // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): + // if( pred ) ptr.x = e0; val = pred ? e0 + // else ptr.x = e1; : e1; + // val = ptr.x; ptr.x = val; + if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { + // Profit on RHS/Loop backedge + if( profit(memphi,2) || + // Else must not be a loop to count profit on LHS. + (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { + Node ld1 = new LoadNode(_name,_declaredType,memphi.in(1),ptr()).peephole(); + Node ld2 = new LoadNode(_name,_declaredType,memphi.in(2),ptr()).peephole(); + return new PhiNode(_name,_type,memphi.region(),ld1,ld2); + } + } + + return null; + } + + // Profitable if we find a matching Store on this Phi arm. + private boolean profit(PhiNode phi, int idx) { + Node px = phi.in(idx); + return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _name.equals(st1._name); + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java new file mode 100644 index 00000000..a0ba973e --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; + +import java.util.BitSet; + +public class LoopNode extends RegionNode { + + public LoopNode( Node entry ) { super(null,entry,null); } + + Node entry() { return in(1); } + Node back () { return in(2); } + + @Override + public String label() { return "Loop"; } + + @Override + public Type compute() { + if( inProgress() ) return Type.CONTROL; + return entry()._type; + } + + @Override + public Node idealize() { + return inProgress() ? null : super.idealize(); + } + + // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) + @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } + @Override Node idom() { return entry(); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java new file mode 100644 index 00000000..138f8fa5 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -0,0 +1,46 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +/** + * Convenience common base for Load and Store. + */ +public abstract class MemOpNode extends Node { + + public final String _name; + + public MemOpNode(String name, Node memSlice, Node memPtr, Node value) { + super(null, memSlice, memPtr, value); + _name = name; + } + + public Node mem() { return in(1); } + public Node ptr() { return in(2); } + + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + if( !_name.equals(other._name) || err()!=null ) return false; + return !(ptr()._type instanceof TypeMemPtr p && + other.ptr()._type instanceof TypeMemPtr q && p._obj!=q._obj); + } + + @Override + boolean eq(Node n) { + MemOpNode mem = (MemOpNode)n; // Invariant + return _name.equals(mem._name); + } + + @Override + int hash() { return _name.hashCode(); } + + @Override + String err() { + Type ptr = ptr()._type; + return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) ) + ? "Might be null accessing '" + _name + "'" + : null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java new file mode 100644 index 00000000..5b65b3fb --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + + +public class MinusNode extends Node { + public MinusNode(Node in) { super(null, in); } + + @Override public String label() { return "Minus"; } + + @Override protected String format() { return "(-%1)"; } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeInteger i0) + return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; + return in(1)._type==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; + } + + @Override + public Node idealize() { + // -(-x) is x + if( in(1) instanceof MinusNode minus ) + return minus.in(1); + + return null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java new file mode 100644 index 00000000..5592c52d --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -0,0 +1,54 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + + +public class MulNode extends Node { + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Mul"; } + + @Override protected String format() { return "(%1*%2)"; } + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()*i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Mul of 1. We do not check for (1*x) because this will already + // canonicalize to (x*1) + if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x * (phi cons)) * con) ? + // Do we have ((x * (phi cons)) * (phi cons)) ? + // Push constant up through the phi: x * (phi con0*con0 con1*con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java new file mode 100644 index 00000000..2743bd19 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -0,0 +1,14 @@ +package com.seaofnodes.simple.node; + +public abstract class MultiNode extends Node { + + public MultiNode(Node... inputs) { super(inputs); } + + // Find a projection by index + ProjNode proj( int idx ) { + for( Node out : _outputs ) + if( out instanceof ProjNode prj && prj._idx==idx ) + return prj; + return null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java new file mode 100644 index 00000000..857920bc --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + + +public class NewNode extends Node { + + TypeMemPtr _ptr; + + public NewNode(TypeMemPtr ptr, Node ctrl) { + super(ctrl); + this._ptr = ptr; + } + + @Override + public String label() { return "new"; } + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p("new ").p(_ptr._obj.str()); + } + + @Override + public Type compute() { return _ptr; } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { return this == n; } + + @Override + int hash() { return _ptr.hashCode(); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java new file mode 100644 index 00000000..d0752af1 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java @@ -0,0 +1,598 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.BaseNode; + +import com.seaofnodes.simple.Parser; + +import com.seaofnodes.simple.IRPrinter; +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; + +import java.util.*; +import java.util.function.Function; + +/** + * All Nodes in the Sea of Nodes IR inherit from the Node class. + * The Node class provides common functionality used by all subtypes. + * Subtypes of Node specialize by overriding methods. + */ +public abstract class Node extends BaseNode implements Cloneable { + + /** + * Inputs to the node. These are use-def references to Nodes. + *

+ * Generally fixed length, ordered, nulls allowed, no unused trailing space. + * Ordering is required because e.g. "a/b" is different from "b/a". + * The first input (offset 0) is often a {@link #isCFG} node. + */ + public ArrayList _inputs; + + /** + * Outputs reference Nodes that are not null and have this Node as an + * input. These nodes are users of this node, thus these are def-use + * references to Nodes. + *

+ * Outputs directly match inputs, making a directed graph that can be + * walked in either direction. These outputs are typically used for + * efficient optimizations but otherwise have no semantics meaning. + */ + public ArrayList _outputs; + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public Type _type; + + /** + * A private Global Static mutable counter, for unique node id generation. + * To make the compiler multithreaded, this field will have to move into a TLS. + * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. + * */ + private static int UNIQUE_ID = 1; + public static int UID() { return UNIQUE_ID; } + + protected Node(Node... inputs) { + super(UNIQUE_ID++); // allocate unique dense ID + _inputs = new ArrayList<>(); + Collections.addAll(_inputs,inputs); + _outputs = new ArrayList<>(); + for( Node n : _inputs ) + if( n != null ) + n.addUse( this ); + } + + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" + public abstract String label(); + + public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } + + public boolean isMultiHead() { return false; } + public boolean isMultiTail() { return false; } + + // ------------------------------------------------------------------------ + // Graph Node & Edge manipulation + + @Override public String typeName() { return _type==null ? null : _type.toString(); } + + /** + * Gets the ith input node + * @param i Offset of the input node + * @return Input node or null + */ + public Node in(int i) { return _inputs.get(i); } + public Node out(int i) { return _outputs.get(i); } + + public int nIns() { return _inputs.size(); } + + public int nOuts() { return _outputs.size(); } + + public boolean isUnused() { return nOuts() == 0; } + + public boolean isMem() { return false; } + + /** + * Change a def into a Node. Keeps the edges correct, by removing + * the corresponding use->def edge. This may make the original + * def go dead. This function is co-recursive with {@link #kill}. + *

+ + * This method is the normal path for altering a Node, because it does the + * proper default edge maintenance. It also immediately kills + * Nodes that lose their last use; at times care must be taken to avoid + * killing Nodes that are being used without having an output Node. This + * definitely happens in the middle of recursive {@link #peephole} calls. + * + * @param idx which def to set + * @param new_def the new definition + * @return new_def for flow coding + */ + public Node setDef(int idx, Node new_def ) { + unlock(); + Node old_def = in(idx); + if( old_def == new_def ) return new_def; // No change + // If new def is not null, add the corresponding def->use edge + // This needs to happen before removing the old node's def->use edge as + // the new_def might get killed if the old node kills it recursively. + if( new_def != null ) + new_def.addUse(this); + if( old_def != null && // If the old def exists, remove a def->use edge + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + // Set the new_def over the old (killed) edge + _inputs.set(idx,new_def); + moveDepsToWorklist(); + // Return self for easy flow-coding + return new_def; + } + + // Remove the numbered input, compressing the inputs in-place. This + // shuffles the order deterministically - which is suitable for Region and + // Phi, but not for every Node. If the def goes dead, it is recursively + // killed, which may include 'this' Node. + Node delDef(int idx) { + unlock(); + Node old_def = in(idx); + if( old_def != null && // If the old def exists, remove a def->use edge + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + old_def.moveDepsToWorklist(); + Utils.del(_inputs, idx); + return this; + } + + /** + * Add a new def to an existing Node. Keep the edges correct by + * adding the corresponding def->use edge. + * + * @param new_def the new definition, appended to the end of existing definitions + * @return new_def for flow coding + */ + Node addDef(Node new_def) { + unlock(); + // Add use->def edge + _inputs.add(new_def); + // If new def is not null, add the corresponding def->use edge + if( new_def != null ) + new_def.addUse(this); + return new_def; + } + + // Breaks the edge invariants, used temporarily + protected void addUse(N n) { _outputs.add(n); } + + // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. + // Return true if the output list is empty afterward. + // Error is 'use' does not exist; ok for 'use' to be null. + protected boolean delUse( Node use ) { + Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. + return _outputs.isEmpty(); + } + + // Shortcut for "popping" n nodes. A "pop" is basically a + // setDef(last,null) followed by lowering the nIns() count. + void popN(int n) { + unlock(); + for( int i=0; iuses, by setting all of its defs + * to null. This may recursively kill more Nodes, and is basically dead + * code elimination. + */ + public void kill( ) { + unlock(); + assert isUnused(); // Has no uses, so it is dead + _type=null; // Flag as dead + while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes + Node old_def = _inputs.removeLast(); + if( old_def != null ) { + IterPeeps.add(old_def);// Revisit neighbor because removed use + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + } + } + assert isDead(); // Really dead now + } + + // Mostly used for asserts and printing. + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + + // Shortcuts to stop DCE mid-parse + // Add bogus null use to keep node alive + public N keep() { addUse(null); return (N)this; } + // Remove bogus null. + public N unkeep() { delUse(null); return (N)this; } + // Test "keep" status + public boolean iskeep() { return Utils.find(_outputs,null) != -1; } + + // Replace self with nnn in the graph, making 'this' go dead + public void subsume( Node nnn ) { + assert nnn!=this; + while( nOuts() > 0 ) { + Node n = _outputs.removeLast(); + n.unlock(); + int idx = Utils.find(n._inputs, this); + n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. + nnn.addUse(n); + } + kill(); + } + + // ------------------------------------------------------------------------ + // Graph-based optimizations + + /** + * Try to peephole at this node and return a better replacement Node. + * Always returns some not-null Node (often this). + */ + public final Node peephole() { + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Node n = peepholeOpt(); + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; + } + + /** + * Try to peephole at this node and return a better replacement Node if + * possible. We compute a {@link Type} and then check and replace: + *

    + *
  • if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}
  • + *
  • in a future chapter we will look for a + * Common Subexpression + * to eliminate.
  • + *
  • we ask the Node for a better replacement. The "better replacement" + * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes + * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing + * math constants, remove algebraic identities and generally simplify the + * code.
  • + * + * Unlike peephole above, this explicitly returns null for no-change, or not-null + * for a better replacement (which can be this). + *
+ */ + public final Node peepholeOpt( ) { + ITER_CNT++; + // Compute initial or improved Type + Type old = setType(compute()); + + // Replace constant computations from non-constants with a constant node + if( !(this instanceof ConstantNode) && _type.isHighOrConst() ) + return new ConstantNode(_type).peepholeOpt(); + + // Global Value Numbering + if( _hash==0 ) { + Node n = GVN.get(this); // Will set _hash as a side effect + if( n==null ) + GVN.put(this,this); // Put in table now + else { + // Because of random worklist ordering, the two equal nodes + // might have different types. Because of monotonicity, both + // types are valid. To preserve monotonicity, the resulting + // shared Node has to have the best of both types. + n.setType(n._type.join(_type)); + _hash = 0; // Clear, since it never went in the table + return deadCodeElim(n);// Return previous; does Common Subexpression Elimination + } + } + + // Ask each node for a better replacement + Node n = idealize(); + if( n != null ) // Something changed + return n; // Report progress + + if( old==_type ) ITER_NOP_CNT++; + return old==_type ? null : this; // Report progress + } + public static int ITER_CNT, ITER_NOP_CNT; + + // m is the new Node, self is the old. + // Return 'm', which may have zero uses but is alive nonetheless. + // If self has zero uses (and is not 'm'), {@link #kill} self. + private Node deadCodeElim(Node m) { + // If self is going dead and not being returned here (Nodes returned + // from peephole commonly have no uses (yet)), then kill self. + if( m != this && isUnused() && !isDead() ) { + // Killing self - and since self recursively kills self's inputs we + // might end up killing 'm', which we are returning as a live Node. + // So we add a bogus extra null output edge to stop kill(). + m.keep(); // Keep m alive + kill(); // Kill self because replacing with 'm' + m.unkeep(); // Okay to peephole m + } + return m; + } + + /** + * This function needs to be + * Monotonic + * as it is part of a Monotone Analysis Framework. + * See for example this set of slides. + *

+ * For Chapter 2, all our Types are really integer constants, and so all + * the needed properties are trivially true, and we can ignore the high + * theory. Much later on, this will become important and allow us to do + * many fancy complex optimizations trivially... because theory. + *

+ * {@link #compute()} needs to be stand-alone, and cannot recursively call compute + * on its inputs, because programs are cyclic (have loops!) and this will just + * infinitely recurse until stack overflow. Instead, compute typically + * computes a new type from the {@link #_type} field of its inputs. + */ + public abstract Type compute(); + + // Set the type. Assert monotonic progress. + // If changing, add users to worklist. + public Type setType(Type type) { + Type old = _type; + assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger + if( old == type ) return old; + _type = type; // Set _type late for easier assert debugging + IterPeeps.addAll(_outputs); + moveDepsToWorklist(); + return old; + } + + /** + * This function rewrites the current Node into a more "idealized" form. + * This is the bulk of our peephole rewrite rules, and we use this to + * e.g. turn arbitrary collections of adds and multiplies with mixed + * constants into a normal form that's easy for hardware to implement. + * Example: An array addressing expression: + *

   ary[idx+1]
+ * might turn into Sea-of-Nodes IR: + *
   (ary+12)+((idx+1) * 4)
+ * This expression can then be idealized into: + *
   ary + ((idx*4) + (12 + (1*4)))
+ * And more folding: + *
   ary + ((idx<<2) + 16)
+ * And during code-gen: + *
   MOV4 Rary,Ridx,16 // or some such hardware-specific notation 
+ *

+ * {@link #idealize} has a very specific calling convention: + *

    + *
  • If NO change is made, return {@code null} + *
  • If ANY change is made, return not-null; this can be {@code this} + *
  • The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. + *
  • Any NEW nodes that are not directly returned DO call {@link #peephole}. + *
+ *

+ * Examples: + * + * + * + * + * + *
before after return comment
{@code (x+5) }{@code (x+5) }{@code null }No change
{@code (5+x) }{@code (x+5) }{@code this }Swapped arguments
{@code ((x+1)+2)}{@code (x+(1+2))}{@code (x+_) }Returns 2 new Nodes
+ * + * The last entry deserves more discussion. The new Node {@code (1+2)} + * created in {@link #idealize} calls {@link #peephole} (which then folds + * into a constant). The other new Node {@code (x+3)} does not call + * peephole, because it is returned and peephole itself will recursively + * call peephole. + *

+ * Since idealize calls peephole and peephole calls idealize, you must be + * careful that all idealizations are monotonic: all transforms + * remove some feature, so that the set of available transforms always + * shrinks. If you don't, you risk an infinite peephole loop! + * + * @return Either a new or changed node, or null for no changes. + */ + public abstract Node idealize(); + + // Some of the peephole rules get complex, and search further afield than + // just the nearest neighbor. These peepholes can fail the pattern match + // on a node some distance away, and if that node ever changes we should + // 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 + * or output of this node, that is, it is at least one step away. The node + * being added must benefit from this node being peepholed. + */ + Node addDep(Node dep) { return addDep(dep,false); } + Node addDepForwards(Node dep) { return addDep(dep,true); } + private Node addDep(Node dep, boolean forwards) { + // 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( !forwards && Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; + _deps.add(dep); + return this; + } + + // Move the dependents onto a worklist, and clear for future dependents. + public void moveDepsToWorklist( ) { + if( _deps==null ) return; + IterPeeps.addAll(_deps); + _deps.clear(); + } + + // Global Value Numbering. Hash over opcode and inputs; hits in this table + // are structurally equal. + public static final HashMap GVN = new HashMap<>(); + + // Two nodes are equal if they have the same inputs and the same "opcode" + // which means the same Java class, plus same internal parts. + @Override public final boolean equals( Object o ) { + if( o==this ) return true; + if( o.getClass() != getClass() ) return false; + Node n = (Node)o; + int len = _inputs.size(); + if( len != n._inputs.size() ) return false; + for( int i=0; i>13) ^ n._nid; + if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing + return (_hash=hash); + } + // Subclasses add extra hash info (such as ConstantNodes constant) + int hash() { return 0; } + + // ------------------------------------------------------------------------ + // Peephole utilities + + // Swap inputs without letting either input go dead during the swap. + Node swap12() { + unlock(); // Hash is order dependent + Node tmp = in(1); + _inputs.set(1,in(2)); + _inputs.set(2,tmp); + return this; + } + + /** + * Does this node contain all constants? Ignores in(0), as is usually + * control. In an input is not a constant, we add dep as a dependency to + * it because dep can make progress if the input becomes a constant later. + * It is sufficient for one of the non-const inputs to have the dependency, + * so we don't bother checking the rest. + */ + boolean allCons(Node dep) { + for( int i=1; i + * See {@link ...} + */ + public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit + + // Find the lowest common ancestor in the current dominator tree. + Node domLCA(Node rhs) { + if( rhs==null ) return this; + Node lhs = this; + while( lhs != rhs ) { + if( lhs==null || rhs==null ) return null; + int comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } + + int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } + // Zero depth means uncached. Check before narrowing so overflow cannot wrap. + final int cacheIDepth(int depth) { + assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + return _idepth = (char)depth; + } + + // Return the immediate dominator of this Node. + Node idom() { return in(0); } + + // Make a shallow copy (same class) of this Node, with given inputs and + // empty outputs and a new Node ID. The original inputs are ignored. + // Does not need to be implemented in isCFG() nodes. + Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + + // Report any post-optimize errors + String err() { return null; } + + /** + * Used to allow repeating tests in the same JVM. This just resets the + * Node unique id generator, and is done as part of making a new Parser. + */ + public static void reset() { + UNIQUE_ID = 1; + GVN.clear(); + ITER_CNT = ITER_NOP_CNT = 0; + } + + // Utility to walk the entire graph applying a function; return the first + // not-null result. + private static final BitSet WVISIT = new BitSet(); + final public E walk( Function pred ) { + assert WVISIT.isEmpty(); + E rez = _walk(pred); + WVISIT.clear(); + return rez; + } + + private E _walk( Function pred ) { + if( WVISIT.get(_nid) ) return null; // Been there, done that + WVISIT.set(_nid); + E x = pred.apply(this); + if( x != null ) return x; + for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; + for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; + return null; + } + + /** + * Debugging utility to find a Node by index + */ + public Node find(int nid) { + return walk( n -> n._nid==nid ? n : null ); + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java new file mode 100644 index 00000000..5e556390 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.*; + +public class NotNode extends Node { + public NotNode(Node in) { super(null, in); } + + @Override public String label() { return "Not"; } + + @Override protected String format() { return "(!%1)"; } + + @Override + public Type compute() { + Type t0 = in(1)._type; + switch( t0 ) { + case TypeInteger i0: + return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; + case TypeMemPtr p0: + // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot + // If input in null then true + // If input is not null ptr then false + if( p0 == TypeMemPtr.TOP ) return TypeInteger.TOP; + if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.constant(1); + if( !p0._nil ) return TypeInteger.constant(0); + return TypeInteger.BOT; + case Type t: + if( t0.getClass() != Type.class ) + // Only doing NOT on ints and ptrs + throw Utils.TODO(); + return t0==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; + } + } + + @Override + public Node idealize() { return null; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java new file mode 100644 index 00000000..24a72aa4 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -0,0 +1,185 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.Utils; + + +public class PhiNode extends Node { + + final String _label; + + // The Phi type we compute must stay within the domain of the Phi. + // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. + final Type _declaredType; + + public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } + + @Override public String label() { return "Phi_"+_label; } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + if( !(region() instanceof RegionNode r) || r.inProgress() ) + p.p("Z"); + p.p("Phi("); + for( Node in : _inputs ) p.n(in).p(","); + return p.unchar(',').close(); + } + + Node region() { return in(0); } + @Override public boolean isMultiTail() { return true; } + @Override + public boolean isMem() { return _declaredType instanceof TypeMem; } + + @Override + public Type compute() { + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? Type.TOP : _type; + // During parsing Phis have to be computed type pessimistically. + if( r.inProgress() ) return _declaredType; + // Set type to local top of the starting type + Type t = _declaredType.glb().dual(); + for (int i = 1; i < nIns(); i++) + // If the region's control input is live, add this as a dependency + // to the control because we can be peeped should it become dead. + if( r.in(i).addDep(this)._type != Type.XCONTROL ) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || r.nIns()<=1 ) + return null; // Input is in-progress + + // If we have only a single unique input, become it. + Node live = singleUniqueInput(); + if (live != null) + return live; + + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; + + // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. + if( nIns()==3 ) { + if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); + if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); + } + // If merging a null-checked null and the checked value, just use the value. + // if( val ) ..; phi(Region,False=0/null,True=val); + // then replace with plain val. + if( nIns()==3 ) { + int nullx = -1; + if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; + if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; + if( nullx != -1 ) { + Node val = in(3-nullx); + if( region().idom() instanceof IfNode iff && iff.pred().addDep(this)==val ) { + // Must walk the idom on the null side to make sure we hit False. + Node idom = region().in(nullx); + while( idom.in(0) != iff ) idom = idom.idom(); + if( idom instanceof ProjNode proj && proj._idx==1 ) + return val; + } + } + } + + return null; + } + + private boolean same_op() { + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode ) return false; + Node busy = null; + for( int i=1; i1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j _print1(ExprPrinter p) { return p.p(_label); } + + @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; } + @Override public boolean isMultiTail() { return in(0).isMultiHead(); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + + public MultiNode ctrl() { return (MultiNode)in(0); } + + @Override + public Type compute() { + Type t = in(0)._type; + return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; + } + + @Override + public Node idealize() { + if ( ctrl() instanceof IfNode iff ) { + if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) + return iff.in(0); // We become our input control + + // Flip a negating if-test, to remove the not + if( iff.pred().addDep(this) instanceof NotNode not ) + return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); + } + return null; + } + + @Override + boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } + + @Override + int hash() { return _idx; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java new file mode 100644 index 00000000..8b52494a --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -0,0 +1,118 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; + +import java.util.ArrayList; +import java.util.Arrays; + +public class RegionNode extends Node { + + public RegionNode(Node... nodes) { super(nodes); } + + @Override + public String label() { return "Region"; } + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p(label()).p(_nid); + } + + @Override public boolean isCFG() { return true; } + @Override public boolean isMultiHead() { return true; } + + @Override + public Type compute() { + if( inProgress() ) return Type.CONTROL; + Type t = Type.XCONTROL; + for (int i = 1; i < nIns(); i++) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( inProgress() ) return null; + // Delete dead paths into a Region + int path = findDeadInput(); + if( path != 0 && + // Do not delete the entry path of a loop (ok to remove the back + // edge and make the loop a single-entry Region which folds away + // the Loop). Folding the entry path confused the loop structure, + // moving the backedge to the entry point. + !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { + // Cannot use the obvious output iterator here, because a Phi + // deleting an input might recursively delete *itself*. This + // shuffles the output array, and we might miss iterating an + // unrelated Phi. So on rare occasions we repeat the loop to get + // all the Phis. + int nouts = 0; + while( nouts != nOuts() ) { + nouts = nOuts(); + for( int i=0; i1 && in(nIns()-1) == null; + } + + // Never equal if inProgress + @Override boolean eq( Node n ) { + return !inProgress(); + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java new file mode 100644 index 00000000..ef4ddb3a --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.*; + + +/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ +public class ReturnNode extends Node { + + public ReturnNode(Node ctrl, Node data, ScopeNode scope) { + super(ctrl, scope.lookup("$mem"), data); + } + + public Node ctrl() { return in(0); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } + + @Override + public String label() { return "Return"; } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + p.p("return "); + p.n(expr()); + return p.p(";"); + } + + @Override public boolean isCFG() { return true; } + + @Override + public Type compute() { + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); + } + + @Override + public Node idealize() { + if( ctrl()._type==Type.XCONTROL ) + return ctrl(); + return null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java new file mode 100644 index 00000000..d567feb5 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -0,0 +1,305 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.*; + +/** + * The Scope node is purely a parser helper - it tracks names to nodes with a + * stack of scopes. + */ +public class ScopeNode extends Node { + + /** + * The control is a name that binds to the currently active control + * node in the graph + */ + public static final String CTRL = "$ctrl"; + public static final String ARG0 = "arg"; + + /** + * Names for every input edge + */ + public final Stack> _scopes; + + /** + * Tracks declared types for every name + */ + public Stack> _types; + + + // A new ScopeNode + public ScopeNode() { + _scopes = new Stack<>(); + _types = new Stack<>(); + _type = Type.BOTTOM; + } + + + @Override public String label() { return "Scope"; } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + p.p("Scope[ "); + String[] names = reverseNames(); + for( int j=0; j syms : _scopes ) + for( String name : syms.keySet() ) + names[syms.get(name)] = name; + return names; + } + + @Override public Type compute() { return Type.BOTTOM; } + + @Override public Node idealize() { return null; } + + public void push() { _scopes.push(new HashMap<>()); _types.push(new HashMap<>()); } + public void pop() { popN(_scopes.pop().size()); _types.pop(); } + + /** + * Create a new name in the current scope + */ + public Node define( String name, Type declaredType, Node n ) { + HashMap syms = _scopes.lastElement(); + _types.lastElement().put(name,declaredType); + if( syms.put(name,nIns()) != null ) + return null; // Double define + return addDef(n); + } + /** + * Lookup a name in all scopes starting from most deeply nested. + * + * @param name Name to be looked up + * @see #update(String, Node, int) + */ + public Node lookup( String name ) { return update(name,null,_scopes.size()-1); } + /** + * If the name is present in any scope, then redefine else null + * + * @param name Name being redefined + * @param n The node to bind to the name + */ + public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); } + /** + * Both recursive lookup and update. + *

+ * A shared implementation allows us to create lazy phis both during + * lookups and updates; the lazy phi creation is part of chapter 8. + * + * @param name Name whose binding is being updated or looked up + * @param n If null, do a lookup, else update binding + * @param nestingLevel The starting nesting level + * @return node being looked up, or the one that was updated + */ + private Node update( String name, Node n, int nestingLevel ) { + if( nestingLevel<0 ) return null; // Missed in all scopes, not found + var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level + var idx = syms.get(name); + if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope + Node old = in(idx); + if( old instanceof ScopeNode loop ) { + // Lazy Phi! + old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() + // Loop already has a real Phi, use it + ? loop.in(idx) + // Set real Phi in the loop head + // The phi takes its one input (no backedge yet) from a recursive + // lookup, which might have insert a Phi in every loop nest. + : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + setDef(idx,old); + } + return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer + } + + // Return declared type + public Type lookupDeclaredType( String name ) { + for( int i=_types.size(); i>0; i-- ) { + Type t = _types.get(i-1).get(name); + if( t != null ) return t; + } + return null; + } + + public Node ctrl() { return in(0); } + + /** + * The ctrl of a ScopeNode is always bound to the currently active + * control node in the graph, via a special name '$ctrl' that is not + * a valid identifier in the language grammar and hence cannot be + * referenced in Simple code. + * + * @param n The node to be bound to '$ctrl' + * + * @return Node that was bound + */ + public Node ctrl(Node n) { return setDef(0,n); } + + /** + * Duplicate a ScopeNode; including all levels, up to Nodes. So this is + * neither shallow (would dup the Scope but not the internal HashMap + * tables), nor deep (would dup the Scope, the HashMap tables, but then + * also the program Nodes). + *

+ * If the {@code loop} flag is set, the edges are filled in as the original + * Scope, as a indication of Lazy Phis at loop heads. The goal here is to + * not make Phis at loop heads for variables which are never touched in the + * loop body. + *

+ * The new Scope is a full-fledged Node with proper use<->def edges. + */ + public ScopeNode dup() { return dup(false); } + public ScopeNode dup(boolean loop) { + ScopeNode dup = new ScopeNode(); + // Our goals are: + // 1) duplicate the name bindings of the ScopeNode across all stack levels + // 2) Make the new ScopeNode a user of all the nodes bound + // 3) Ensure that the order of defs is the same to allow easy merging + for( HashMap syms : _scopes ) + dup._scopes.push(new HashMap<>(syms)); + for( HashMap ts : _types ) + dup._types.push(ts); // Types don't change, just keep stacks aligned + + dup.addDef(ctrl()); // Control input is just copied + for( int i=1; i _print1(ExprPrinter p) { + return p.p(label()); + } + + @Override public boolean isCFG() { return true; } + @Override public boolean isMultiHead() { return true; } + + @Override + public TypeTuple compute() { return _args; } + + @Override + public Node idealize() { return null; } + + // No immediate dominator, and idepth==0 + @Override int idepth() { return 0; } + @Override Node idom() { return null; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java new file mode 100644 index 00000000..4b4e9615 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -0,0 +1,75 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.IterPeeps; + + +public class StopNode extends Node { + + public final String _src; + + public StopNode(String src) { + super(); + _src = src; + } + + @Override + public String label() { + return "Stop"; + } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + if( ret()!=null ) return p.n(ret()); + p.p("Stop[ "); + for( Node ret : _inputs ) + p.n(ret).p(" "); + return p.p("]"); + } + + @Override public boolean isCFG() { return true; } + + // If a single Return, return it. + // Otherwise, null because ambiguous. + public ReturnNode ret() { + return nIns()==1 ? (ReturnNode)in(0) : null; + } + + @Override + public Type compute() { + return Type.BOTTOM; + } + + @Override + public Node idealize() { + int len = nIns(); + for( int i=0; i { + String msg = n.err(); + if( msg != null && obs != null ) obs.error(n, msg); + return msg; + }); + if( err != null ) throw new RuntimeException(err); + if( obs != null ) obs.phase("TypeCheck"); + return this; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java new file mode 100644 index 00000000..71eb0842 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -0,0 +1,67 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.*; + + +/** + * Store represents setting a value to a memory based object, in chapter 10 + * this means a field inside a struct. + */ +public class StoreNode extends MemOpNode { + + /** + * @param name The struct field we are assigning to + * @param memSlice The whole-memory node - this is updated after a Store + * @param memPtr The ptr to the struct where we will store a value + * @param value Value to be stored + */ + public StoreNode(String name, Node memSlice, Node memPtr, Node value) { + super(name, memSlice, memPtr, value); + } + + @Override + public String label() { return "Store"; } + @Override + public boolean isMem() { return true; } + public Node val() { return in(3); } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + return p.p(".").p(_name).p("=").p( val()).p(";"); + } + + @Override + public Type compute() { return TypeMem.BOT; } + + @Override + public Node idealize() { + + // Simple store-after-store on same address. Should pick up the + // required init-store being stomped by a first user store. + if( mem() instanceof StoreNode st && + ptr()==st.ptr() && _name.equals(st._name) && // Must check same object + ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers + // Must have exactly one use of "this" or you get weird + // non-serializable memory effects in the worse case. + st.checkNoUseBeyond(this) ) { + setDef(1,st.mem()); + return this; + } + + return null; + } + + // Check that `this` has no uses beyond `that` + private boolean checkNoUseBeyond(Node that) { + if( nOuts()==1 ) return true; + // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that + // when the other uses go away we can retry. + for( Node use : _outputs ) + if( use != that ) + use.addDep(that); + return false; + } + +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java new file mode 100644 index 00000000..6fa87a07 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -0,0 +1,58 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + + +public class SubNode extends Node { + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Sub"; } + + @Override protected String format() { return "(%1-%2)"; } + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + // Sub of same is 0 + if( in(1)==in(2) ) + return TypeInteger.ZERO; + + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()-i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Keep invalid operands for type checking. + if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { + if( y.isConstant() && y.value()==0 ) return lhs; + if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); + } + + // x - (-y) is x+y + if( rhs instanceof MinusNode minus ) + return new AddNode(lhs,minus.in(1)); + + // (-x) - y is -(x+y) + if( lhs instanceof MinusNode minus ) + return new MinusNode(new AddNode(minus.in(1),rhs).peephole()); + + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } +} 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..92d970d8 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; + +/** Chapter 10's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter { + + @Override protected String[] edgeNames(Node n) { + return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; + } + @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : -1; } + + @Override protected Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; + 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; + } + + @Override protected 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..1980659e --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,45 @@ +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); + _phase = "TypeCheck"; + _parser.STOP.typeCheck(); + } + @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 Node scope() { return _parser._scope; } + @Override protected void roots(ArrayList roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java new file mode 100644 index 00000000..cdc58215 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java @@ -0,0 +1,59 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Represents a field in a struct. This is not a Type in the type system. + */ +public class Field extends Type { + + // The pair {fieldName,type} uniquely identifies a field. + + // Field name + public final String _fname; + // Type of the field + public final Type _type; + + private Field(String fname, Type type ) { + super(TFLD); + _fname = fname; + _type = type; + } + public static Field make( String fname, Type type ) { + return new Field(fname,type).intern(); + } + + public static final Field TEST = make("test",TypeInteger.ZERO); + public static void gather(ArrayList ts) { ts.add(TEST); } + + @Override Field xmeet( Type that ) { + Field fld = (Field)that; // Invariant + assert _fname.equals(fld._fname); + return make(_fname,_type.meet(fld._type)); + } + + @Override + public Field dual() { return make(_fname,_type.dual()); } + + @Override + public Field glb() { + return make(_fname,_type.glb()); + } + + // Override in subclasses + int hash() { return _fname.hashCode() ^ _type.hashCode(); } + + boolean eq(Type t) { + Field f = (Field)t; + return Utils.eq(_fname,f._fname) && _type==f._type; + } + + + @Override + public StringBuilder print( StringBuilder sb ) { + return _type.print(sb.append(_fname).append(":")); + } + + @Override public String str() { return _fname; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java new file mode 100644 index 00000000..bd9cf458 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java @@ -0,0 +1,187 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * These types are part of a Monotone Analysis Framework, + * @see see for example this set of slides. + *

+ * The types form a lattice; @see a symmetric complete bounded (ranked) lattice. + *

+ * This wild lattice theory will be needed later to allow us to easily beef up + * the analysis and optimization of the Simple compiler... but we don't need it + * now, just know that it is coming along in a later Chapter. + *

g + * One of the fun things here is that while the theory is deep and subtle, the + * actual implementation is darn near trivial and is generally really obvious + * what we're doing with it. Right now, it's just simple integer math to do + * simple constant folding e.g. 1+2 == 3 stuff. + */ + +public class Type { + static final HashMap INTERN = new HashMap<>(); + + // ---------------------------------------------------------- + // Simple types are implemented fully here. "Simple" means: the code and + // type hierarchy are simple, not that the Type is conceptually simple. + static final byte TBOT = 0; // Bottom (ALL) + static final byte TTOP = 1; // Top (ANY) + static final byte TCTRL = 2; // Ctrl flow bottom + static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) + static final byte TSIMPLE = 4; // End of the Simple Types + static final byte TINT = 5; // All Integers; see TypeInteger + static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM = 7; // Whole-memory token + static final byte TMEMPTR = 8; // Memory pointer type + static final byte TSTRUCT = 9; // Structs; tuples with named fields + static final byte TFLD =10; // Fields into struct + + public final byte _type; + + public boolean is_simple() { return _type < TSIMPLE; } + private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; + protected Type(byte type) { _type = type; } + + public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL + public static final Type TOP = new Type( TTOP ).intern(); // ANY + public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl + public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl + public static Type[] gather() { + ArrayList ts = new ArrayList<>(); + ts.add(BOTTOM); + ts.add(CONTROL); + Field.gather(ts); + TypeInteger.gather(ts); + TypeMem.gather(ts); + TypeMemPtr.gather(ts); + TypeStruct.gather(ts); + TypeTuple.gather(ts); + int sz = ts.size(); + for( int i = 0; i < sz; i++ ) + ts.add(ts.get(i).dual()); + return ts.toArray(new Type[ts.size()]); + } + + // Is high or on the lattice centerline. + public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } + + // Strict constant values, things on the lattice centerline. + // Excludes both high and low values + public boolean isConstant() { return false; } + + /** + * Display Type name in a format that's good for IR printer + */ + public StringBuilder typeName( StringBuilder sb) { return print(sb); } + + public Type makeInit() { return null; } + + // ---------------------------------------------------------- + + // Notes on Type interning. + // At the moment it is not easy to reset the interned types + // because we hold static references to several types and these are scattered + // around. This means the INTERN cache will retain all types from + // every run of the Parser. For this to work correctly types must be + // rigorous about defining when they are the same. Also types need to be + // immutable once defined. + // The rationale for interning is performance. + + // Factory method which interns "this" + public T intern() { + T nnn = (T)INTERN.get(this); + if( nnn==null ) + INTERN.put(nnn=(T)this,this); + return nnn; + } + + private int _hash; // Hash cache; not-zero when set. + @Override + public final int hashCode() { + if( _hash!=0 ) return _hash; + _hash = hash(); + if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing + return _hash; + } + // Override in subclasses + int hash() { return _type; } + + @Override + public final boolean equals( Object o ) { + if( o==this ) return true; + if( !(o instanceof Type t)) return false; + if( _type != t._type ) return false; + return eq(t); + } + // Overridden in subclasses; subclass can assume "this!=t" and java classes are same + boolean eq(Type t) { return true; } + + + // ---------------------------------------------------------- + public final Type meet(Type t) { + // Shortcut for the self case + if( t == this ) return this; + // Same-type is always safe in the subclasses + if( _type==t._type ) return xmeet(t); + // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". + if( is_simple() ) return this.xmeet(t ); + if( t.is_simple() ) return t .xmeet(this); + return Type.BOTTOM; // Mixing 2 unrelated types + } + + // Compute meet right now. Overridden in subclasses. + // Handle cases where 'this.is_simple()' and unequal to 't'. + // Subclassed xmeet calls can assert that '!t.is_simple()'. + Type xmeet(Type t) { + assert is_simple(); // Should be overridden in subclass + // ANY meet anything is thing; thing meet ALL is ALL + if( _type==TBOT || t._type==TTOP ) return this; + if( _type==TTOP || t._type==TBOT ) return t; + // 'this' is {TCTRL,TXCTRL} + if( !t.is_simple() ) return BOTTOM; + // 't' is {TCTRL,TXCTRL} + return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; + } + + public Type dual() { + return switch( _type ) { + case TBOT -> TOP; + case TTOP -> BOTTOM; + case TCTRL -> XCONTROL; + case TXCTRL -> CONTROL; + default -> throw Utils.TODO(); // Should not reach here + }; + } + + // ---------------------------------------------------------- + // Our lattice is defined with a MEET and a DUAL. + // JOIN is dual of meet of both duals. + public final Type join(Type t) { + if( this==t ) return this; + return dual().meet(t.dual()).dual(); + } + + // True if this "isa" t; e.g. 17 isa TypeInteger.BOT + public boolean isa( Type t ) { return meet(t)==t; } + + /** + * Compute greatest lower bound in the lattice + */ + public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; } + + // ---------------------------------------------------------- + // Useful in the debugger, which calls toString everywhere. + // This is a more verbose dev-friendly print. + @Override + public final String toString() { + return print(new StringBuilder()).toString(); + } + + public StringBuilder print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} + + // This is used by error messages, and is a shorted print. + public String str() { return STRS[_type]; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java new file mode 100644 index 00000000..8c77672b --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -0,0 +1,101 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** + * Integer Type + */ +public class TypeInteger extends Type { + + public final static TypeInteger TOP = make(false, 0); + public final static TypeInteger BOT = make(false, 1); + public final static TypeInteger ZERO= make(true , 0); + + public final boolean _is_con; + + /** + * The constant value or + * if not constant then 1=bottom, 0=top. + */ + public final long _con; + + private TypeInteger(boolean is_con, long con) { + super(TINT); + _is_con = is_con; + _con = con; + } + public static TypeInteger make(boolean is_con, long con) { + return new TypeInteger(is_con,con).intern(); + } + + public static TypeInteger constant(long con) { return make(true, con); } + + public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); } + + // FIXME this display format is problematic + // In visualizer '#' gets prepended if its a constant + @Override + public StringBuilder print(StringBuilder sb) { + if( this==TOP ) return sb.append("IntTop"); + if( this==BOT ) return sb.append("IntBot"); + return sb.append(_con); + } + + @Override public String str() { + if( this==TOP ) return "~int"; + if( this==BOT ) return "int"; + return ""+_con; + } + + /** + * Display Type name in a format that's good for IR printer + */ + @Override + public StringBuilder typeName(StringBuilder sb) { + if( this==TOP ) return sb.append("IntTop"); + if( this==BOT ) return sb.append("IntBot"); + return sb.append("Int"); + } + + @Override + public boolean isHighOrConst() { return _is_con || _con==0; } + + @Override + public boolean isConstant() { return _is_con; } + + public long value() { return _con; } + + @Override + public Type xmeet(Type other) { + // Invariant from caller: 'this' != 'other' and same class (TypeInteger) + TypeInteger i = (TypeInteger)other; // Contract + // BOT wins + if ( this==BOT ) return this; + if ( i ==BOT ) return i ; + // TOP loses + if ( i ==TOP ) return this; + if ( this==TOP ) return i ; + // Since both are constants, and are never equals (contract) unequals + // constants fall to bottom + return BOT; + } + + @Override + public Type dual() { + if( isConstant() ) return this; // Constants are a self-dual + return _con==0 ? BOT : TOP; + } + + @Override + public Type glb() { return BOT; } + + @Override public TypeInteger makeInit() { return ZERO; } + + @Override + int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); } + @Override + public boolean eq( Type t ) { + TypeInteger i = (TypeInteger)t; // Contract + return _con==i._con && _is_con==i._is_con; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java new file mode 100644 index 00000000..d921bac3 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** A token for all memory; field values are not tracked in its type. */ +public class TypeMem extends Type { + private final boolean _high; + private TypeMem(boolean high) { super(TMEM); _high = high; } + public static final TypeMem TOP = new TypeMem(true ).intern(); + public static final TypeMem BOT = new TypeMem(false).intern(); + + public static void gather(ArrayList ts) { ts.add(BOT); } + @Override TypeMem xmeet(Type t) { return BOT; } + @Override public Type dual() { return _high ? BOT : TOP; } + @Override public Type glb() { return BOT; } + @Override int hash() { return _high ? 1 : 2; } + @Override boolean eq(Type t) { return _high==((TypeMem)t)._high; } + @Override public StringBuilder print(StringBuilder sb) { return sb.append(_high ? "MemTop" : "MemBot"); } + @Override public String str() { return print(new StringBuilder()).toString(); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java new file mode 100644 index 00000000..6c874a93 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -0,0 +1,82 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** + * Represents a Pointer to memory. + * + * Null is generic pointer to non-existent memory. + * *void is a non-Null pointer to all possible structs. + * Pointers can be to specific struct types, or a union with Null. + * The distinguished *$BOT ptr represents union of *void and Null. + * The distinguished *$TOP ptr represents the dual of *$BOT. + */ +public class TypeMemPtr extends Type { + // A TypeMemPtr is pair (obj,nil) + // where obj is one of + // (null,TypeStruct). + // where nil is one of + // (true,false) meaning an explicit null is allowed or not + + public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY + public final boolean _nil; + + private TypeMemPtr(TypeStruct obj, boolean nil) { + super(TMEMPTR); + _obj = obj; + _nil = nil; + } + public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } + public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); } + + public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); + public static final TypeMemPtr TOP = BOT.dual(); + public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); + public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null + public static final TypeMemPtr TEST= make(TypeStruct.TEST,false); + public static void gather(ArrayList ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); } + + @Override + Type xmeet(Type t) { + TypeMemPtr that = (TypeMemPtr) t; + return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil); + } + + @Override + public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); } + + @Override + public Type glb() { + if( _obj==null ) return BOT; + return make(_obj.glb(),true); + } + @Override public TypeMemPtr makeInit() { return NULLPTR; } + + @Override + int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); } + + @Override + boolean eq(Type t) { + TypeMemPtr ptr = (TypeMemPtr)t; // Invariant + return _obj == ptr._obj && _nil == ptr._nil; + } + + // [void,name,MANY]*[,?] + @Override + public StringBuilder print(StringBuilder sb) { + if( this==BOT ) return sb.append("PtrBot"); + if( this==TOP ) return sb.append("PtrTop"); + if( this== NULLPTR) return sb.append("null"); + if( this== VOIDPTR) return sb.append("*void"); + return _obj.print(sb.append("*")).append(_nil ? "?" : ""); + } + + @Override public String str() { + if( this==BOT ) return "PtrBot"; + if( this==TOP ) return "PtrTop"; + if( this== NULLPTR) return "null"; + if( this== VOIDPTR) return "*void"; + return "*"+_obj.str()+(_nil ? "?" : ""); + } + +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java new file mode 100644 index 00000000..44741def --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -0,0 +1,127 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Represents a struct type. + */ +public class TypeStruct extends Type { + + // For now in terms of the lattice + // a struct type just stays as is + + public final String _name; + public final Field[] _fields; + + private TypeStruct(String name, Field[] fields) { + super(Type.TSTRUCT); + _name = name; + _fields = fields; + } + + public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); } + public static TypeStruct make(String name, ArrayList fields) { return make(name,fields.toArray(new Field[fields.size()])); } + + public static final TypeStruct TOP = make("$TOP",new Field [0]); + public static final TypeStruct BOT = make("$BOT",new Field [0]); + public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); + public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); } + + public int find(String fname) { + for( int i=0; i<_fields.length; i++ ) + if( _fields[i]._fname.equals(fname) ) + return i; + return -1; + } + + @Override + Type xmeet(Type t) { + TypeStruct that = (TypeStruct) t; + if( this==TOP ) return that; + if( that==TOP ) return this; + if( this==BOT ) return BOT; + if( that==BOT ) return BOT; + + // Within the same compilation unit, struct names are unique. If the + // names differ, its different structs. Across many compilation units, + // structs with the same name but different field layouts can be + // interned... which begs the question: + // "What is the meet of structs from two different compilation units?" + // And the answer is: "don't ask". + if( !_name.equals(that._name) ) + return BOT; // It's a struct; that's about all we know + // Now all fields should be the same, so just do field meets + assert _fields.length == that._fields.length; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = (Field)_fields[i].meet(that._fields[i]); + return make(_name,flds); + } + + @Override + public TypeStruct dual() { + if( this==TOP ) return BOT; + if( this==BOT ) return TOP; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].dual(); + return make(_name,flds); + } + + // Keeps the same struct, but lower-bounds all fields. + @Override + public TypeStruct glb() { + if( _glb() ) return this; + // Need to glb each field + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].glb(); + return make(_name,flds); + } + private boolean _glb() { + for( Field f : _fields ) + if( f.glb() != f ) + return false; + return true; + } + + @Override + boolean eq(Type t) { + TypeStruct ts = (TypeStruct)t; // Invariant + if( !_name.equals(ts._name) ) + return false; + if( _fields.length!=ts._fields.length ) + return false; + for( int i = 0; i < _fields.length; i++ ) + if( _fields[i] != ts._fields[i] ) + return false; + return true; + } + + @Override + int hash() { + long hash = _name.hashCode(); + for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode(); + return Utils.fold(hash); + } + + @Override + public StringBuilder print(StringBuilder sb) { + if( this==BOT ) return sb.append("StructBot"); + if( this==TOP ) return sb.append("StructTop"); + sb.append(_name).append(" {\n"); + for( Field f : _fields ) { + sb.append(" ").append(f._fname).append(":"); + f._type.print(sb); + sb.append(";\n"); + } + return sb.append("}"); + } + + @Override public String str() { + if( this==BOT ) return "StructBot"; + if( this==TOP ) return "StructTop"; + return _name; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java new file mode 100644 index 00000000..0aa019a7 --- /dev/null +++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -0,0 +1,88 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +public class TypeTuple extends Type { + + public final Type[] _types; + + private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } + public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } + + private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); + public static void gather(ArrayList ts) { ts.add(TEST); } + + @Override + Type xmeet(Type other) { + TypeTuple tt = (TypeTuple)other; // contract from xmeet + assert _types.length == tt._types.length; + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].meet(tt._types[i]); + return make(ts); + } + + @Override public Type dual() { + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].dual(); + return make(ts); + } + + @Override + public Type glb() { + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].glb(); + return make(ts); + } + + @Override + public StringBuilder print(StringBuilder sb) { + sb.append("["); + for( Type t : _types ) + t.print(sb).append(","); + sb.setLength(sb.length()-1); + sb.append("]"); + return sb; + } + + @Override public String str() { return print(new StringBuilder()).toString(); } + + /** + * Display Type name in a format that's good for IR printer + */ + @Override + public StringBuilder typeName(StringBuilder sb) { + sb.append("["); + for( Type t : _types ) + t.typeName(sb).append(","); + sb.setLength(sb.length()-1); + sb.append("]"); + return sb; + } + + public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); + public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); + public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); + public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); + + @Override + int hash() { + int sum = 0; + for( Type type : _types ) sum ^= type.hashCode(); + return sum; + } + + @Override + boolean eq( Type t ) { + TypeTuple tt = (TypeTuple)t; // Contract + if( _types.length != tt._types.length ) return false; + for( int i=0; i<_types.length; i++ ) + if( _types[i]!=tt._types[i] ) + return false; + return true; + } + + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java new file mode 100644 index 00000000..89882b2f --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -0,0 +1,100 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter01Test { + + @Test + public void testSimpleProgram() { + Parser parser = new Parser("return 1;"); + StopNode stop = parser.parse(); + StartNode start = Parser.START; + ReturnNode ret = (ReturnNode)stop.in(0); + + assertTrue(ret.ctrl() instanceof ProjNode); + Node expr = ret.expr(); + if( expr instanceof ConstantNode con ) { + assertEquals(start,con.in(0)); + assertEquals(TypeInteger.constant(1), con._type); + } else { + fail(); + } + } + + @Test + public void testZero() { + Parser parser = new Parser("return 0;"); + parser.parse(); + StartNode start = Parser.START; + for( Node use : start._outputs ) + if( use instanceof ConstantNode con && con._type instanceof TypeInteger ) + assertEquals(TypeInteger.constant(0),con._type); + } + + @Test + public void testBad1() { + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'ret'",e.getMessage()); + } + } + + @Test + public void testBad2() { + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } + } + + @Test + public void testNotBad3() { + // this test used to fail in chapter 1 + assertEquals("return 12;", new Parser("return --12;").parse().print()); + } + + @Test + public void testBad4() { + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } + } + + @Test + public void testNotBad5() { + // this test used to fail in chapter 1 + assertEquals("return -100;", new Parser("return -100;").parse().print()); + } + + @Test + public void testBad6() { + try { + new Parser("return100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'return100'",e.getMessage()); + } + } + + @Test + public void testBad7() { + try { + new Parser("return 1;}").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, unexpected }",e.getMessage()); + } + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java new file mode 100644 index 00000000..a6075b75 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter02Test { + + @Test + public void testParseGrammar() { + Parser parser = new Parser("return 1+2*3+-5;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @Test + public void testAddPeephole() { + Parser parser = new Parser("return 1+2;"); + StopNode ret = parser.parse(); + assertEquals("return 3;", ret.print()); + } + + @Test + public void testSubPeephole() { + Parser parser = new Parser("return 1-2;"); + StopNode ret = parser.parse(); + assertEquals("return -1;", ret.print()); + } + + @Test + public void testMulPeephole() { + Parser parser = new Parser("return 2*3;"); + StopNode ret = parser.parse(); + assertEquals("return 6;", ret.print()); + } + + @Test + public void testDivPeephole() { + Parser parser = new Parser("return 6/3;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @Test + public void testMinusPeephole() { + Parser parser = new Parser("return 6/-3;"); + StopNode ret = parser.parse(); + assertEquals("return -2;", ret.print()); + } + + @Test + public void testExample() { + Parser parser = new Parser("return 1+2*3+-5;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java new file mode 100644 index 00000000..609dff76 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -0,0 +1,69 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter03Test { + + @Test + public void testVarDecl() { + Parser parser = new Parser("int a=1; return a;"); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testVarAdd() { + Parser parser = new Parser("int a=1; int b=2; return a+b;"); + StopNode ret = parser.parse(); + assertEquals("return 3;", ret.print()); + } + + @Test + public void testVarScope() { + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;"); + StopNode ret = parser.parse(); + assertEquals("return 4;", ret.print()); + } + + @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); "); + StopNode ret = parser.parse(); + assertEquals("return 8;", ret.print()); + } + + @Test + public void testSelfAssign() { + try { + new Parser("int a=a; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'a'",e.getMessage()); + } + } + + @Test + public void testRedeclareVar() { + try { + new Parser("int a=1; int b=2; int c=0; int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Redefining name 'b'",e.getMessage()); + } + } + + @Test + public void testBad1() { + try { + new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected }: ",e.getMessage()); + } + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java new file mode 100644 index 00000000..edc62f50 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -0,0 +1,165 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter04Test { + + @Test + public void testPeephole() { + Parser parser = new Parser("return 1+arg+2; "); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testPeephole2() { + Parser parser = new Parser("return (1+arg)+2;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testAdd0() { + Parser parser = new Parser("return 0+arg;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void testAddAddMul() { + Parser parser = new Parser("return arg+0+arg;"); + StopNode ret = parser.parse(); + assertEquals("return (arg*2);", ret.print()); + } + + @Test + public void testPeephole3() { + Parser parser = new Parser("return 1+arg+2+arg+3; "); + StopNode ret = parser.parse(); + assertEquals("return ((arg*2)+6);", ret.print()); + } + + @Test + public void testMul1() { + Parser parser = new Parser("return 1*arg;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void testSub0() { + Parser parser = new Parser("return arg-0;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void test0Sub() { + Parser parser = new Parser("return 0-arg;"); + StopNode ret = parser.parse(); + assertEquals("return (-arg);", ret.print()); + } + + @Test + public void testSubNeg() { + Parser parser = new Parser("return 3--arg;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testNegSub() { + Parser parser = new Parser("return -arg-3;"); + StopNode ret = parser.parse(); + assertEquals("return (-(arg+3));", ret.print()); + } + + @Test + public void testVarArg() { + Parser parser = new Parser("return arg; "); + StopNode stop = parser.parse(); + ReturnNode ret = stop.ret(); + assertTrue(ret.in(0) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); + } + + @Test + public void testConstantArg() { + 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; "); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testCompEq2() { + 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; "); + StopNode ret = parser.parse(); + assertEquals("return 0;", ret.print()); + } + + @Test + public void testCompNEq2() { + 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; "); + 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; "); + StopNode ret = parser.parse(); + assertEquals("return (arg+1);", ret.print()); + } + + @Test + public void testBug3() { + try { + new Parser("inta=1; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'inta'",e.getMessage()); + } + } + + @Test + public void testBug4() { + Parser parser = new Parser("return -arg;"); + StopNode ret = parser.parse(); + assertEquals("return (-arg);", ret.print()); + } + + @Test + public void testBug5() { + Parser parser = new Parser("return arg--2;"); + StopNode ret = parser.parse(); + assertEquals("return (arg--2);", ret.print()); + } +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java new file mode 100644 index 00000000..cf7bd6cf --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -0,0 +1,228 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter05Test { + + @Test + public void testIfStmt() { + Parser parser = new Parser( +""" +int a = 1; +if (arg == 1) + a = arg+2; +else { + a = arg-3; + +} + +return a;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); + } + + @Test + public void testTest() { + Parser parser = new Parser( +""" +int c = 3; +int b = 2; +if (arg == 1) { + b = 3; + c = 4; +} +return c;""", TypeInteger.BOT); + StopNode ret = parser.parse().iterate(); + assertEquals("return Phi(Region17,4,3);", ret.toString()); + } + + @Test + public void testReturn2() { + Parser parser = new Parser( +""" +if( arg==1 ) + return 3; +else + return 4; +""", TypeInteger.BOT); + StopNode stop = parser.parse(); + assertEquals("Stop[ return 3; return 4; ]", stop.toString()); + } + + @Test + public void testIfMergeB() { + Parser parser = new Parser( +""" +int a=arg+1; +int b=0; +if( arg==1 ) + b=a; +else + b=a+1; +return a+b;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); + } + + @Test + public void testIfMerge2() { + Parser parser = new Parser( +""" +int a=arg+1; +int b=arg+2; +if( arg==1 ) + b=b+a; +else + a=b+1; +return a+b;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + } + + @Test + public void testMerge3() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( arg==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); + } + + @Test + public void testMerge4() { + Parser parser = new Parser( +""" +int a=0; +int b=0; +if( arg ) + a=1; +if( arg==0 ) + b=2; +return arg+a+b; +""", TypeInteger.BOT); + StopNode stop = parser.parse(); + assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,2,0));", stop.toString()); + } + + @Test + public void testMerge5() { + Parser parser = new Parser( +""" +int a=arg==2; +if( arg==1 ) +{ + a=arg==3; +} +return a;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return (arg==Phi(Region17,3,2));", ret.toString()); + } + + @Test + public void testMergeSameCon() { + Parser parser = new Parser( +""" +int a=arg; +if( arg==1 ) + a=3; +else + a=3; +return a; +"""); + StopNode ret = parser.parse(); + assertEquals("return 3;", ret.toString()); + } + + @Test + public void testTrue() { + StopNode stop = new Parser("return true;").parse(); + assertEquals("return 1;",stop.toString()); + } + + @Test + public void testHalfDef() { + try { + new Parser("if( arg==1 ) int b=2; return b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); + } + } + + @Test + public void testHalfDef2() { + try { + new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'b'",e.getMessage()); + } + } + + @Test + public void testRegress1() { + try { + new Parser("if(arg==2) int a=1; else int b=2; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); + } + } + + + @Test + public void testBadNum() { + try { + new Parser("return 1-;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); + } + } + + @Test + public void testKeyword1() { + try { + new Parser("int true=0; return true;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'true'",e.getMessage()); + } + } + + @Test + public void testKeyword2() { + try { + new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'else'",e.getMessage()); + } + } + + @Test + public void testKeyword3() { + try { + new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'ififif'",e.getMessage()); + } + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java new file mode 100644 index 00000000..920e26e3 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -0,0 +1,294 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter06Test { + + @Test + public void testPeepholeReturn() { + Parser parser = new Parser( +""" +if( true ) return 2; +return 1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 2;", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @Test + public void testPeepholeRotate() { + Parser parser = new Parser( +""" +int a = 1; +if (arg) + a = 2; +return (arg < a) < 3; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((arg 4 ) + break; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return arg;", stop.toString()); + } + + @Test + public void testRegress2() { + Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testBreakOutsideLoop() { + try { + new Parser(""" +if(arg <= 10) { + break; + arg = arg + 1; +} +return arg; +""").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("No active loop for a break or continue",e.getMessage()); + } + } + + @Test + public void testRegress3() { + Parser parser = new Parser(""" +while(arg < 10) { + break; +} +return arg; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return arg;", stop.toString()); + } + + @Test + public void testRegress4() { + Parser parser = new Parser(""" +int a = 1; +while(arg < 10) { + a = a + 1; + if (arg > 2) { + int a = 17; + break; + } +} +return a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region29,Phi(Loop8,1,(Phi_a+1)),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + } + + @Test + public void testRegress5() { + Parser parser = new Parser(""" +int a = 1; +while(1) { + a = a + 1; + if (a<10) continue; + break; +} +return a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return (Phi(Loop8,1,Add)+1);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java new file mode 100644 index 00000000..1b1920e8 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -0,0 +1,301 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.Ignore; + +import static org.junit.Assert.*; + +public class Chapter09Test { + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +int v0=0; +arg=0; +while(v0) { + while(1) if(arg*arg*0==0) {} + while(0) {} + arg=1; +} +return 0; + """); + StopNode stop = parser.parse().iterate(); + } + + @Test + public void testGVN1() { + Parser parser = new Parser( + """ +int x = arg + arg; +if(arg < 10) { + return arg + arg; +} +else { + x = x + 1; +} +return x; + """); + StopNode stop = parser.parse(); + assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); + Assert.assertEquals(2L, Evaluator.evaluate(stop, 1)); + Assert.assertEquals(23L, Evaluator.evaluate(stop, 11)); + } + + @Test + public void testGVN2() { + Parser parser = new Parser( + """ +return arg*arg-arg*arg; + """); + StopNode stop = parser.parse(); + assertEquals("return 0;", stop.toString()); + Assert.assertEquals(0L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist1() { + Parser parser = new Parser( + """ +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); + Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist2() { + Parser parser = new Parser( + """ +int cond = 0; +int one = 1; +while (arg < 10) { + if (cond) one = 2; + arg = arg + one*3 + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); + Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist3() { + Parser parser = new Parser( + """ +int v1 = 0; +int v2 = 0; +int v3 = 0; +int v4 = 0; +int v5 = 0; +int v6 = 0; +int v7 = 0; +int v8 = 0; +while (arg) { + if (v1) v2 = 1; + if (v2) v3 = 1; + if (v3) v4 = 1; + if (v4) v5 = 1; + if (v5) v6 = 1; + if (v6) v7 = 1; + if (v7) v8 = 1; + arg = arg + v8 + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop15,arg,(Phi_arg+1));", stop.toString()); + } + + @Test + public void testRegionPeepBug() { + Parser parser = new Parser( + """ +int v0=0; +int v1=0; +while(v1+arg) { + arg=0; + int v2=v0; + while(arg+1) {} + v0=1; + v1=v2; +} + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testWhile0() { + Parser parser = new Parser("while(0) continue; if(0) arg=0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testWhile1() { + Parser parser = new Parser(""" +if(0) while(0) { + int arg=arg; + while(0) {} +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testPrecedence() { + Parser parser = new Parser("return 3-1+2;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + } + + @Test + public void testSwap2() { + Parser parser = new Parser("return 1+(1+1);"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3;", stop.toString()); + } + + + @Test + public void testFuzz0() { + Parser parser = new Parser(""" +int one = 1; +int a = 0; +int zero = 0; +while(arg) { + a = -(one + a + 2); + arg = arg + 1; + one = one + zero; +} +return a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,0,(-(Phi_a+3)));", stop.toString()); + } + + @Test + public void testFuzz1() { + Parser parser = new Parser(""" +while(1) {} +while(arg) break; +while(arg) arg=0; +arg=0; +int v0=0!=0<-0; +return -0+0+0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testFuzz2() { + Parser parser = new Parser("return 0+-0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testFuzz3() { + Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz4() { + Parser parser = new Parser(""" +while(1) { + arg=0<=0; + if(1<0) while(arg==-0) arg=arg-arg; +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz5() { + Parser parser = new Parser(""" +{ + int v0=0; + while(1) + int v1=0--0; + while(v0) + break; + while(-v0) { + while(0+0+v0) continue; + break; + } + if(-0!=-0+0+v0) while(0+0+0+0) + break; +} +return 0!=0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz6() { + Parser parser = new Parser(""" +int v0=0; +while(0==1) while(v0) + v0=1+v0; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz7() { + Parser parser = new Parser(""" +while(1) {} +int v0=0; +while(v0) + {} +int v1=0; +while(1) + v1=1; +return v1+v0; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz8() { + Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop7,arg,(Phi_arg-1));", stop.toString()); + } + + @Test + public void testMeet() { + Type t1 = Type.TOP; + Type t2 = TypeInteger.TOP; + Assert.assertEquals(TypeInteger.TOP, t1.meet(t2)); + Assert.assertEquals(TypeInteger.TOP, t2.meet(t1)); + t1 = Type.BOTTOM; + t2 = TypeInteger.BOT; + Assert.assertEquals(Type.BOTTOM, t1.meet(t2)); + Assert.assertEquals(Type.BOTTOM, t2.meet(t1)); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java new file mode 100644 index 00000000..f39b1e78 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -0,0 +1,581 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter10Test { + @Test public void testDeadReferenceReturn() { + for( String body : new String[] { + "return new S; return 0;", + "if(1) return new S; else return 0;" + } ) { + String src = "struct S { int x; }; " + body; + var stop = new Parser(src).parse().iterate(); + org.junit.Assert.assertTrue(body, + stop.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); + } + } + + + + // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. + private static final String NULLABLE_POINT_SOURCE = """ + struct Point { int x; }; + Point point = new Point; + point.x = 42; + Point? p = null; + if (arg) p = point; + """; + + @Test + public void testNullGuards() { + for( String body : new String[] { + "if (p != null) return p.x; return -1;", + "if (null != p) return p.x; return -1;", + "if (!!!!p) return p.x; return -1;", + "if (!!!p) return -1; else return p.x;", + "int b = !!p; if (b) return p.x + b - 1; return -1;" + } ) { + StopNode stop = new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + assertEquals(body,-1L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); + assertEquals(body,42L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); + } + } + + @Test + public void testNullGuardErrors() { + for( String body : new String[] { + "return p.x;", + "if (p == null) return p.x; return -1;", + "if (!!point) return p.x; return -1;", + "if (!!p) { int x = p.x; } return p.x;" + } ) { + try { + new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + fail(body); + } catch( RuntimeException e ) { + // Known null is rejected during parsing in these chapters. + String expected = body.startsWith("if (p == null)") + ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'"; + assertEquals(body,expected,e.getMessage()); + } + } + } + + @Test + public void testFuzzer() { + Parser parser = new Parser( +""" +int a = arg/3; +int b = arg*5; +int x = arg*7; +int y = arg/11; +int p; int g; int h; +if( (arg/13)==0 ) { + p = x + y; + g = x; + h = y; +} else { + p = a + b; + g = a; + h = b; +} +int r = g+h; +return p-r; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testStruct() { + Parser parser = new Parser(""" +struct Bar { + int a; + int b; +} +struct Foo { + int x; +} +Foo? foo = null; +Bar bar = new Bar; +bar.a = 1; +bar.a = 2; +return bar.a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 2;", stop.toString()); + } + + @Test + public void testExample() { + Parser parser = new Parser(""" +struct Vector2D { int x; int y; } +Vector2D v = new Vector2D; +v.x = 1; +if (arg) + v.y = 2; +else + v.y = 3; +return v; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return new Vector2D;", stop.toString()); + } + + @Test + public void testBug() { + Parser parser = new Parser(""" +struct s0 { + int v0; +} +s0? v1=null; +int v3=v1.zAicm; +"""); + try { parser.parse(); fail(); } + catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); } + } + + @Test + public void testBug2() { + Parser parser = new Parser(""" +struct s0 { int v0; } +arg=0+new s0.0; +"""); + try { parser.parse(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test + public void testLoop() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = new Bar; +while (arg) { + bar.a = bar.a + 2; + arg = arg + 1; +} +return bar.a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,0,(Phi_$mem+2));", stop.toString()); + } + + @Test + public void testIf() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = new Bar; +if (arg) bar = null; +bar.a = 1; +return bar.a; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); } + } + + @Test + public void testIf2() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar? bar = null; +if (arg) bar = new Bar; +bar.a = 1; +return bar.a; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); } + } + + @Test + public void testIf3() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = null; +if (arg) bar = null; +bar.a = 1; +return bar.a; +"""); + try { parser.parse(); fail(); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); } + } + + @Test + public void testIfOrNull() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +if( bar ) bar.a = 1; +return bar; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region15,null,new Bar);", stop.toString()); + } + + @Test + public void testIfOrNull2() { + Parser parser = new Parser( +""" +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +int rez = 3; +if( !bar ) rez=4; +else bar.a = 1; +return rez; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region32,4,3);", stop.toString()); + } + + @Test + public void testWhileWithNullInside() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0? v0 = new s0; +int ret = 0; +while(arg) { + ret = v0.v0; + v0 = null; + arg = arg - 1; +} +return ret; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); } + } + + @Test + public void testRedeclareStruct() { + Parser parser = new Parser(""" +struct s0 { + int v0; +} +s0? v1=new s0; +s0? v1; +v1=new s0; +"""); + try { parser.parse(); fail(); } + catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); } + } + + @Test + public void testIter() { + // Build and use an iterator + Parser parser = new Parser( +""" +struct Iter { + int x; + int len; +} +Iter i = new Iter; +i.len = arg; +int sum=0; +while( i.x < i.len ) { + sum = sum + i.x; + i.x = i.x + 1; +} +return sum; +"""); + StopNode stop = parser.parse().iterate(); + // The preceding len store keeps the initial x load on the bulk chain. + assertEquals("return Phi(Loop13,0,(Phi(Loop,.x,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals(10L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,5)); + } + + + + + @Test + public void test1() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +while(arg) { + s0 v0 = new s0; + v0.v0 = arg; + arg = arg-1; + if (arg==5) ret=v0; +} +return ret; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString()); + } + + @Test + public void test2() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +s0 v0 = new s0; +while(arg) { + v0.v0 = arg; + arg = arg-1; + if (arg==5) ret=v0; +} +return ret; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); + } + + + @Test + public void test3() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +while(arg < 10) { + s0 v0 = new s0; + if (arg == 5) ret=v0; + arg = arg + 1; +} +return ret; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString()); + } + + @Test + public void testBug3() { + Parser parser = new Parser(""" +struct s0 { + int f0; +} +if(0>=0) return new s0; +return new s0; +int v0=null.f0; +"""); + try { parser.parse(); fail(); } + catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); } + } + + @Test + public void testBug4() { + Parser parser = new Parser(""" +if(0) { + while(0) if(arg) continue; + int v0=0; + while(1) { + int arg=-arg; + v0=arg; + } +} + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testBug5() { + Parser parser = new Parser(""" +struct s0 { + int f0; +} +if(0) return 0; +else return new s0; +if(new s0.f0) return 0; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return new s0;", stop.toString()); + } + + @Test + public void testBug6MissedWorklist() { + Parser parser = new Parser(""" +while(0) {} +int v4=0; +while(0 0) { + s.x = s.x + 1; + int j = 3; + while (j > 0) { + j = j - 1; + if (j == 1) continue; + s.y = s.y + arg; + if (arg == 2) break; + s.z = s.z + 1; + } + arg = arg - 1; + } + return s; + """).parse().iterate(); + for (int arg=0; arg<7; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(new Object[] { + 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0) + }, obj.fields()); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S s = new S; + s.x = 3; s.y = 5; s.z = 7; + if (arg) { s.x = 11; s.z = 13; return s; } + s.y = 17; + return s; + """).parse().iterate(); + for (int arg=0; arg<2; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields()); + } + } + + @Test public void testDropLoads() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java new file mode 100644 index 00000000..019140ad --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.fuzzer.Fuzzer; +import org.junit.Test; +import org.junit.Ignore; + + +import static org.junit.Assert.assertTrue; + +/** + * Normal tests run chapter-local regression seeds. Open failures and exploratory + * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore). + * Seeds depend on this chapter's generator; do not copy them between chapters. + */ +public class FuzzerWrap { + + private static final long[] REGRESSION_SEEDS = { + }; + + private static final long[] OPEN_FAILING_SEEDS = { + }; + + @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); } + @Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); } + + private static void fuzzPeepsSeeds(long... seeds) { + var fuzzer = new Fuzzer(); + for (long seed : seeds) + fuzzer.fuzzPeepsRegression(seed); + } + + @Test + @Ignore + public void fuzzPeepsLarge() { + var fuzzer = new Fuzzer(); + for (int i=0; i<1000000; i++) + fuzzer.fuzzPeeps(i); + assertTrue(fuzzer.noExceptions()); + } + + + @Test + @Ignore + public void fuzzPeepTiming() { + var fuzzer = new Fuzzer(); + int max_nid=0; + for (int i=0; i<1000000; i++) + max_nid = fuzzer.fuzzPeepTiming(i, max_nid); + } +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java new file mode 100644 index 00000000..ad595822 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -0,0 +1,73 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SchedulerTest { + + private static void assertObj(Object obj, String name, Object... fields) { + assertEquals(Evaluator.Obj.class, obj.getClass()); + var o = (Evaluator.Obj)obj; + assertEquals(name, o.struct()._name); + Assert.assertArrayEquals(fields, o.fields()); + } + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode stop = parser.parse();//.iterate(); + var eval = new Evaluator(stop); + assertEquals(2L, eval.evaluate(1, 10)); + } + + @Test + public void testStoreInIf() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v0=new S; +if(arg) v0.f=1; +return v0; +"""); + StopNode stop = parser.parse().iterate(); + var eval = new Evaluator(stop); + assertObj(eval.evaluate(0, 10), "S", 0L); + assertObj(eval.evaluate(1, 10), "S", 1L); + } + + @Test + public void testStoreInIf2() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; +"""); + 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/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java new file mode 100644 index 00000000..c9eb2747 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -0,0 +1,132 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.type.*; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +public class TypeTest { + + // Test basic properties and GLB + @Test + public void testTypeAdHoc() { + TypeStruct s1 = TypeStruct.make("s1", new Field[]{ + Field.make("a", TypeInteger.BOT), + Field.make("b", TypeInteger.BOT) }); + TypeStruct s2 = TypeStruct.make("s2", new Field[]{ + Field.make("a", TypeInteger.BOT), + Field.make("b", TypeInteger.BOT) }); + Assert.assertEquals(s1, s1.glb()); + Assert.assertNotEquals(s1, s1.dual()); + Assert.assertEquals(s1, s1.dual().glb()); + + assertEquals(TypeStruct.BOT, s1.meet(s2)); + assertEquals(TypeMem.BOT, TypeMem.TOP.meet(TypeMem.BOT)); + assertEquals(TypeMem.TOP, TypeMem.BOT.dual()); + assertEquals(TypeMem.BOT, TypeMem.TOP.dual()); + + TypeMemPtr ptr1 = TypeMemPtr.make(s1); + Assert.assertEquals(s1, ptr1._obj); + TypeMemPtr ptr2 = TypeMemPtr.make(s2); + Assert.assertEquals(s2, ptr2._obj); + + TypeMemPtr ptr1nil = TypeMemPtr.make(s1, true); + Assert.assertEquals(s1, ptr1nil._obj); + Assert.assertTrue(ptr1nil._nil); + Assert.assertFalse(ptr1nil._obj==null); + TypeMemPtr ptr2nil = TypeMemPtr.make(s2, true); + Assert.assertEquals(s2, ptr2nil._obj); + + Assert.assertNotEquals(ptr1, ptr2); + Assert.assertNotEquals(ptr1, ptr1.glb()); + Assert.assertEquals(ptr1nil, ptr1.glb()); + + Assert.assertEquals(ptr1, ptr1.dual().dual()); + Assert.assertEquals(ptr1.glb(), ptr1.dual().glb()); + Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil)); + Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR)); + + TypeMemPtr TOP = TypeMemPtr.TOP; + TypeMemPtr BOT = TypeMemPtr.BOT; + TypeMemPtr PTR = TypeMemPtr.VOIDPTR; + TypeMemPtr NULL = TypeMemPtr.NULLPTR; + Type PTR_meet_NULL = NULL.meet(PTR); + Assert.assertEquals(BOT, PTR_meet_NULL); + Type ptr1_meet_ptr2 = ptr1.meet(ptr2); + Assert.assertEquals(PTR, ptr1_meet_ptr2); + Type NULL_join_ptr1 = NULL.join(ptr1); + Assert.assertEquals(TOP, NULL_join_ptr1); + Type NULL_join_PTR = PTR.join(NULL); + Assert.assertEquals(TOP, NULL_join_PTR); + Type ptr1_dual = ptr1.dual(); + Type nullableptr1_dual = ptr1nil.dual(); + } + + // Test theoretical properties. + // This is a symmetric complete bounded (ranked) lattice. + // Also the meet is commutative and associative. + // The lattice has a dual (symmetric), and join is ~(~x meet ~y). + // See https://en.wikipedia.org/wiki/Lattice_(order). + @Test + public void testLatticeTheory() { + Type[] ts = Type.gather(); + + // Confirm commutative & complete + for( Type t0 : ts ) + for( Type t1 : ts ) { + check_commute (t0,t1); + check_symmetric(t0,t1); + } + + // Confirm associative + for( Type t0 : ts ) + for( Type t1 : ts ) + for( Type t2 : ts ) + assoc(t0,t1,t2); + + // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C) + for( Type t0 : ts ) + for( Type t1 : ts ) + if( t0.isa(t1) ) + for( Type t2 : ts ) { + Type t02 = t0.join(t2); + Type t12 = t1.join(t2); + Type mt = t02.meet(t12); + assertSame(mt,t12); + } + } + + // By design in meet, args are already flipped to order _type, which forces + // symmetry for things with badly ordered _type fields. The question is + // still interesting for other orders. + private static void check_commute( Type t0, Type t1 ) { + if( t0==t1 ) return; + if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order + Type mta = t0.meet(t1); + Type mtb = t1.meet(t0); // Reverse args and try again + assertSame(mta,mtb); + } + + // A & B = MT + // Expect: ~A & ~MT == ~A + // Expect: ~B & ~MT == ~B + private static void check_symmetric( Type t0, Type t1 ) { + if( t1==t0 ) return; + Type mt = t0.meet(t1); + Type ta = mt.dual().meet(t1.dual()); + Type tb = mt.dual().meet(t0.dual()); + assertSame(ta,t1.dual()); + assertSame(tb,t0.dual()); + } + + private static void assoc( Type t0, Type t1, Type t2 ) { + Type t01 = t0 .meet(t1 ); + Type t12 = t1 .meet(t2 ); + Type t01_2 = t01.meet(t2 ); + Type t0_12 = t0 .meet(t12); + assertSame(t01_2,t0_12); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java new file mode 100644 index 00000000..bddcc596 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -0,0 +1,209 @@ +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.TypeStruct; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Objects; + +public class Evaluator { + + private static final Object MEMORY = new Object(); + + public record Obj(TypeStruct struct, Object[] fields) { + @Override + public String toString() { + var sb = new StringBuilder(); + sb.append("Obj<").append(struct._name).append("> {"); + for(int i=0; i= 0 ) return idx; + throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name); + } + + public enum Status { + TIMEOUT, + FALLTHROUGH + } + + /** + * Find the start node from some node in the graph or null if there is no start node + */ + private static StartNode findStart(BitSet visit, Node node) { + if (node == null) return null; + if( visit.get(node._nid) ) return null; + visit.set(node._nid); + StartNode start = node instanceof StartNode ? (StartNode) node : null; + for( Node def : node._inputs ) { + var res = findStart(visit,def); + if( res != null ) start = res; + } + for( Node use : node._outputs ) { + var res = findStart(visit,use); + if( res != null ) start = res; + } + return start; + } + + private final Object[] values; + private final ArrayList phiCache = new ArrayList<>(); + private final StartNode start; + private final Scheduler.Block startBlock; + + public Evaluator(Node graph) { + var visited = new BitSet(); + this.start = findStart(visited, graph); + if (this.start == null) { + this.values = null; + this.startBlock = null; + return; + } + this.startBlock = Scheduler.schedule(this.start); + this.values = new Object[visited.length()]; + } + + private long div(DivNode div) { + long in2 = vall(div.in(2)); + return in2 == 0 ? 0 : vall(div.in(1)) / in2; + } + + private Object alloc(NewNode alloc) { + var type = ((TypeMemPtr)alloc.compute())._obj; + return new Obj(type, new Object[type._fields.length]); + } + + private Object load(LoadNode load) { + var from = (Obj)val(load.in(2)); + var idx = getFieldIndex(from.struct, load); + return from.fields[idx]; + } + + private Object store(StoreNode store) { + var to = (Obj)val(store.in(2)); + var val = val(store.in(3)); + var idx = getFieldIndex(to.struct, store); + to.fields[idx] = val; + return null; + } + + private static boolean isTrue(Object obj) { + if (obj == null) return false; + if (obj instanceof Number n) return n.longValue() != 0; + return true; + } + + private Object val(Node node) { + return values[node._nid]; + } + + private long vall(Node node) { + var v = val(node); + if (v instanceof Number n) return n.longValue(); + throw new AssertionError("Not a long " + v); + } + + private Object cons(ConstantNode cons) { + var type = cons.compute(); + if (type instanceof TypeInteger i) return i.value(); + assert type instanceof TypeMemPtr; + return null; + } + + private Object exec(Node node) { + return switch (node) { + case ConstantNode cons -> cons(cons); + case AddNode add -> vall(add.in(1)) + vall(add.in(2)); + case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; + case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; + case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; + case DivNode div -> div(div); + case MinusNode minus -> -vall(minus.in(1)); + case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); + case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; + case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); + case CastNode cast -> val(cast.in(1)); + case LoadNode load -> load(load); + case StoreNode store -> store(store); + case NewNode alloc -> alloc(alloc); + case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; + default -> throw new AssertionError("Unexpected node " + node); + }; + } + + /** + * Run the graph until either a return is found or the number of loop iterations are done. + */ + public Object evaluate(long parameter, int loops) { + if (start == null) return Status.TIMEOUT; + var s = new Object[start.compute()._types.length]; + s[1] = MEMORY; + s[2] = parameter; + values[start._nid] = s; + int i=0; + Scheduler.Block block = this.startBlock; + for(;;) { + for(; i 0 && region.nIns() > exit; + block = block.next()[0]; + assert block != null; + for (; i < block.nodes().length; i++) { + if (!(block.nodes()[i] instanceof PhiNode)) break; + phiCache.add(val(block.nodes()[i].in(exit))); + } + for (i=0; i + *
+ * This scheduler is used for tests and is not the final scheduler. + *
+ *
+ * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. + * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy + * this scheduler might place initialization code in loops. + *
+ * We schedule late here since memory edges have some implicit information. There can only be one + * memory edge (for a field) at a time. However, before if statements this might split. + * When scheduling early a store might be moved out of the loop but then there are two edges alive. + * The one through the store node and the adjacent one. This is an invalid schedule and instead of + * handling this case a late scheduler does not have this problem as memory edges are combined + * through phi nodes. + *
+ *
+ * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. + * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes + * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. + */ +public class Scheduler { + + /** + * A basic block with a schedule of the containing nodes. + * + * @param nodes The nodes in the final schedule of this block + * @param exit The exit node of this block. + * @param exitId In case the next block is a region this gives the entry id into the region. + * @param next The next blocks. + */ + public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} + + /** + * Definition of a block in the building process. + */ + private static class BasicBlock { + + /** + * Dominator of this block + */ + final BasicBlock dom; + /** + * Id of this block where a.depth < b.depth means that a is before b or + * both are in different branches. + */ + final int depth; + /** + * Previous blocks + */ + final BasicBlock[] prev; + /** + * Entry to this block + */ + final Node entry; + /** + * Schedules node in reverse order. + */ + final ArrayList reverseSchedule = new ArrayList<>(); + + /** + * Initialize the block. + * @param entry The node that started this block + * @param depth The depth of this node. All previous nodes need to have a lower value. + * @param prev The previous blocks. + */ + BasicBlock(Node entry, int depth, BasicBlock... prev) { + BasicBlock dom = null; + int max = -1; + for(var bb: prev) { + max = Math.max(max, bb.depth); + dom = dom==null?bb:dom(dom, bb); + } + assert depth > max; + this.dom = dom; + this.depth = depth; + this.entry = entry; + this.prev = prev; + } + + } + + /** + * Ancillary data for nodes used during the scheduling process. + */ + private static class NodeData { + + /** + * The node this ancillary data is used for. + */ + final Node node; + + /** + * Number of alive users not yet scheduled. + */ + int users = 1; + + /** + * Block into which the node should be placed so far. + * This will be updated with every placed use. + */ + BasicBlock block; + + /** + * Initialize ancillary data for a node + * @param node The node this ancillary data is associated with. + */ + NodeData(Node node) { + this.node = node; + } + + } + + /** + * Ancillary data for nodes. + */ + private final IdentityHashMap data = new IdentityHashMap<>(); + /** + * List of nodes which can be placed since all users are placed. + */ + private final Stack scheduleQueue = new Stack<>(); + + /** + * Get the ancillary data for an alive node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node. + */ + NodeData d(Node node) { + return od(node).orElseThrow(AssertionError::new); + } + + /** + * Get the ancillary data for a potentially dead node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node an alive node or + * Optional.empty() for null or dead nodes. + */ + Optional od(Node node) { + return Optional.ofNullable(data.get(node)); + } + + /** + * Checks a node for validity + * @param data The node to check. + * @return true if all placed inputs are before this node. + */ + private boolean isValid(NodeData data) { + return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d); + } + + /** + * Checks that node is not XCtrl. + * @param node The node to check. + * @return true if the node is not XCtrl. + */ + private static boolean isNotXCtrl(Node node) { + return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); + } + + /** + * Return the dominator of a and b + * @param a Block a + * @param b Block b + * @return The dominator of a and b + */ + private static BasicBlock dom(BasicBlock a, BasicBlock b) { + while (a!=b) { + if (a.depth >= b.depth) a=a.dom; + if (b.depth > a.depth) b=b.dom; + } + return a; + } + + /** + * Return if a is before b. + * @param a Block a + * @param b Block b + * @return true is a is before b. + */ + private static boolean isBefore(BasicBlock a, BasicBlock b) { + while (b.depth > a.depth) { + while (b.dom.depth > a.depth) b = b.dom; + for (int i=0; i{ + // before might be in a different branch. So check this case with isBefore + // and only refine the placement if it is not in a different branch. + if (isBefore(d.block, data.block)) refinePlacement(data, d.block); + }); + } + + /** + * Schedule all nodes not yet scheduled. + */ + private void doSchedule() { + while (!scheduleQueue.isEmpty()) { + var data = scheduleQueue.pop(); + assert !isPinnedNode(data); + + assert data.block != null; + assert data.users == 0; + + if (data.node instanceof LoadNode l) { + assert isValid(data); + // Handle anti-deps of load nodes. + // At this point all anti-dep nodes are scheduled, + // but they did not refine the placement + // so do that now. + var mem = l.in(1); + for(var out : mem._outputs) { + if (out instanceof PhiNode p) { + var r = p.in(0); + for (int i = 1; i < p.nIns(); i++) { + if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); + } + } else if (!(out instanceof LoadNode)) { + optionalRefinePlacement(data, out); + } + } + } + + assert isValid(data); + data.block.reverseSchedule.add(data.node); + + for(var in:data.node._inputs) { + if (in!=null) update(d(in), data.block); + } + if (data.node instanceof StoreNode s) { + // Store nodes have anti-deps to load nodes. + // So decrease the uses of these loads when the store is placed. + for (var out: s.in(1)._outputs) { + if (out instanceof LoadNode) od(out).ifPresent(this::decUsers); + } + } + } + + // Now all nodes should be placed and have a block assigned + assert this.data.values().stream().noneMatch(d->d.block==null); + } + + /** + * Decrement the not-yet-placed users of a block. + * @param data The node for which the users should be decremented. + */ + private void decUsers(NodeData data) { + assert data.users > 0; + if (--data.users == 0) { + assert data.block != null; + assert isValid(data); + + // When all users are gone this node can be scheduled. + scheduleQueue.push(data); + } + } + + /** + * Update the placement of a node + * @param data The node to update + * @param block The block before which the node should happen. + */ + private void update(NodeData data, BasicBlock block) { + assert block != null; + if (data.users == 0) { + assert isPinnedNode(data); + return; + } + refinePlacement(data, block); + decUsers(data); + } + + /** + * Checks if a CFG node is ready to be placed. This is used for regions joining branches. + * @param node The CFG node to check + * @return true if all parents of a CFG node are placed. + */ + private boolean isCFGNodeReady(Node node) { + if (node instanceof LoopNode l) { + assert d(l.in(1)).block != null; + } else if (node instanceof RegionNode r) { + for(int i=1; id.block==null).orElse(false)) return false; + } + } else { + assert d(node.in(0)).block != null; + } + return true; + } + + /** + * Schedule all phi nodes. + * @param phiQueue List of all the phi nodes to schedule. + */ + private void schedulePhis(Stack phiQueue) { + while(!phiQueue.empty()) { + var data = phiQueue.pop(); + var phi = (PhiNode) data.node; + var r = phi.in(0); + data.block = d(r).block; + for(int i=1; iupdate(d(in), d.block)); + } + } + } + + /** + * Build the control flow graph. + * @param start The start node. + */ + private void doBuildCTF(StartNode start) { + var queue = new Stack(); + var phiQueue = new Stack(); + queue.push(d(start)); + int depth = 0; + while (!queue.isEmpty()) { + var data = queue.pop(); + var node = data.node; + assert node.isCFG(); + BasicBlock block; + switch (node) { + case StartNode s: + block = new BasicBlock(s, depth++); + break; + case LoopNode l: + block = new BasicBlock(l, depth++, d(l.in(1)).block); + break; + case RegionNode r: + var prev = new ArrayList(); + for(int i=1; iprev.add(d.block)); + } + block = new BasicBlock(r, depth++, prev.toArray(BasicBlock[]::new)); + break; + case IfNode i: + block = d(i.in(0)).block; + break; + case ReturnNode r: + block = d(r.in(0)).block; + break; + default: + block = new BasicBlock(node, depth++, d(node.in(0)).block); + } + data.block = block; + if (node instanceof RegionNode r) { + // Regions might have phis which need to be scheduled. + // Put them on a list for later scheduling. + for (var out:r._outputs) { + if (out instanceof PhiNode p) { + var d = this.data.get(p); + if (d!=null) { + d.users=0; + phiQueue.push(d); + } + } + } + } + if (!(node instanceof ReturnNode)) + for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); + var b = block; + for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); + } + + // Schedule all phi nodes. + schedulePhis(phiQueue); + } + + /** + * Mark a node alive and create ancillary data for it. + * @param queue List into which this node should be placed if it needs to be visited. + * @param node Node which should be marked alive. + * @param cfg If this node is a CFG node. + */ + private void markAlive(Stack queue, Node node, boolean cfg) { + var nd = od(node).orElse(null); + if (nd != null) { + // Node was already visited, just increase the users. + if (nd.users>0) nd.users++; + return; + } + assert node.isCFG() == cfg; + assert isNotXCtrl(node); + nd = new NodeData(node); + if (cfg) nd.users=0; + data.put(node, nd); + queue.push(nd); + } + + /** + * Mark all alive nodes. + * First all CFG nodes are marked, then all other nodes. + * @param node THe start node. + */ + private void doMarkAlive(Node node) { + var cfgQueue = new Stack(); + var dataQueue = new Stack(); + var mem = new Stack(); + markAlive(cfgQueue, node, true); + // Mark all CFG nodes. + while (!cfgQueue.isEmpty()) { + var data = cfgQueue.pop(); + node = data.node; + assert node.isCFG(); + if (!(node instanceof ReturnNode)) { + for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); + } + for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); + } + // Mark all other nodes. + while (!dataQueue.isEmpty()) { + var data = dataQueue.pop(); + node = data.node; + assert !node.isCFG(); + if (node instanceof PhiNode phi) { + var r = phi.in(0); + for (int i=1; id.users++); + } + } + } + + /** + * Helper function to append the nodes of a block to an array of nodes + * @param arr The array the nodes of a block should be appended to. + * @param node A potentially separator node + * @param block The block from which the nodes should be appended. + * @return The combined new array. + */ + private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { + var idx = arr.length; + arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); + if (node != null) arr[idx++] = node; + for(int i=block.reverseSchedule.size()-1;i>=0;i--) { + arr[idx++] = block.reverseSchedule.get(i); + } + return arr; + } + + /** + * Find the CFG output of a node + * @param node The node + * @return THe CFG output of the node. + */ + private static Node findSingleCFGOut(Node node) { + if (node instanceof StartNode) { + for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; + return null; + } + assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; + for(var n:node._outputs) if(n.isCFG()) return n; + return null; + } + + /** + * Build the final data structure after all the scheduling happened. + * @param start The start node + * @return The final schedule. + */ + private Block build(Node start) { + var blocks = new IdentityHashMap(); + var queue = new Stack(); + queue.push(d(start)); + + // Visit all CFG nodes and create blocks for them. + // This can combine blocks. + while(!queue.isEmpty()) { + var data = queue.pop(); + var first = data.node; + Node prev; + var last = first; + Node[] arr = new Node[0]; + if (first instanceof RegionNode) { + var nodes = new ArrayList(); + for(var out: first._outputs) { + if (out instanceof PhiNode) { + if(od(out).isPresent()) nodes.add(out); + } + } + arr = nodes.toArray(arr); + } + arr = appendNodes(arr, null, data.block); + assert !(first instanceof IfNode); + for(;;) { + prev = last; + last = findSingleCFGOut(last); + if (last == null) break; + if (last instanceof RegionNode) { + if (blocks.get(last) == null && last != first) + queue.push(d(last)); + break; + } + if (last instanceof ReturnNode) break; + if (last instanceof IfNode if_) { + for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out)); + break; + } + data = d(last); + arr = appendNodes(arr, data.node, data.block); + } + blocks.put(first, switch(last) { + case null -> new Block(arr, null, -1, new Block[0]); + case IfNode i -> new Block(arr, i, -1, new Block[2]); + case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); + case ReturnNode r -> new Block(arr, r, -1, new Block[0]); + default -> throw new AssertionError("Unexpected block exit node"); + }); + } + // Update the next pointer of all blocks. + for (var block: blocks.values()) { + switch(block.exit) { + case null: break; + case IfNode i: + for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); + break; + case RegionNode r: + block.next[0] = blocks.get(r); + break; + default: + assert block.exit instanceof ReturnNode; + } + } + // And return the block for the start node. + return blocks.get(start); + } + + /** + * Create a schedule for the program reachable from start. + * @param start The start node + * @return The final schedule. + */ + public static Scheduler.Block schedule(StartNode start) { + var scheduler = new Scheduler(); + scheduler.doMarkAlive(start); + scheduler.doBuildCTF(start); + scheduler.doSchedule(); + return scheduler.build(start); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java new file mode 100644 index 00000000..7dcb88e5 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -0,0 +1,199 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.Random; +import java.util.function.Consumer; + +/** + * Implementation of the fuzzer. + * Random testcases are generated using the ScriptGenerator. + * This will generate a script similar to how the parser parses it but instead of parsing it will + * randomly choose a variation the parser would parse and generate it. + * These scripts are the parsed by the parser and all exceptions are caught and filtered + * to only show one occurrence of one problem. This includes script which generate different + * results when graphs optimized with different worklist seeds are executed. + * To aid debugging scripts that cause errors are then reduced by applying rules and checking + * that the same issue persists. + */ +public class Fuzzer { + + private static final int EVAL_TIMEOUT = 1000; + + /** + * List of exceptions already encountered. + * This is used to filter new exceptions and discard them if they are already found. + */ + private final ArrayList exceptions = new ArrayList<>(); + + /** + * Filter and record an exception in a script. + * @param e The exception caused by the script + * @param script The script causing the exception + * @param reproducer Reproducer which should be used to reproduce the test case. + */ + private void recordException(Throwable e, String script, Consumer reproducer, long seed) { + for (var ex : exceptions) { + if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; + } + exceptions.add(e); + System.out.println("========== Stack =========="); + e.printStackTrace(System.out); + System.out.println("========== Seed =========="); + System.out.println(seed); + System.out.println("========== Code ==========="); + System.out.println(script); + System.out.println("========= Reduced ========="); + System.out.println(Reducer.reduce(script, e, reproducer)); + System.out.println("==========================="); + System.out.flush(); + } + + private static boolean neq(Object a, Object b) { + if (Objects.equals(a, b)) return false; + if (a instanceof Evaluator.Obj ea && b instanceof Evaluator.Obj eb) { + if (ea.struct() != eb.struct()) return true; + for(int i=0;irunCheck(s, valid), seed); + } + } + + /** + * Run one test with the given seed, and check for peephole counts. + * @param seed The seed to use for generating this test case + */ + public int fuzzPeepTiming(long seed, int max_nids) { + var rand = new Random(seed); + var sb = new StringBuilder(); + new ScriptGenerator(rand, sb, true).genProgram(); + try { + var parser = new Parser(sb.toString()); + + var stop = parser.parse(); + int parse_peeps= Node.ITER_CNT; + int parse_nops = Node.ITER_NOP_CNT; + double parse_nop_ratio = (double)parse_nops/parse_peeps; + int nids = Node.UID(); + if( nids <= max_nids ) return max_nids; + + double parse_peeps_per_node = (double)parse_peeps/nids; + + stop.iterate(); + int iter_peeps= Node.ITER_CNT; + int iter_nops = Node.ITER_NOP_CNT; + double iter_nop_ratio = (double)iter_nops/iter_peeps; + double iter_peeps_per_node = (double)iter_peeps/nids; + + System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n", + seed, + parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node, + iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node); + return nids; + } catch (Throwable e) { + // ignore exceptions, let the normal fullPeeps catch and reduce, + // we're interested in valid program optimizations + } + return max_nids; + } + + + /** + * Run one test with the given seed. + * @param seed The seed to use for generating this test case + */ + public void fuzzPeeps(long seed) { + var rand = new Random(seed); + var sb = new StringBuilder(); + var valid = new ScriptGenerator(rand, sb, true).genProgram(); + check(sb.toString(), valid, seed); + } + + + /** + * Check that no exceptions happened. + * @return true if no exception happened + */ + public boolean noExceptions() { + return exceptions.isEmpty(); + } + + /** Run a fixed chapter-local seed without reducing or swallowing failures. */ + public void fuzzPeepsRegression(long seed) { + var rand = new Random(seed); + var sb = new StringBuilder(); + var valid = new ScriptGenerator(rand, sb, true).genProgram(); + try { + runCheck(sb.toString(), valid); + } catch (Throwable e) { + AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed); + ae.initCause(e); + throw ae; + } + } +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java new file mode 100644 index 00000000..f947a234 --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -0,0 +1,142 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.BitSet; + +/** + * Some utilities for the fuzzer. + */ +class FuzzerUtils { + + /** + * Get the val of a private field + */ + @SuppressWarnings("unchecked") + public static T getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { + return (T) (obj instanceof Class clz ? getField(clz, name).get(null) : getField(obj.getClass(), name).get(obj)); + } + + /** + * Get access to a private field + */ + public static Field getField(Class clz, String name) throws NoSuchFieldException { + var field = clz.getDeclaredField(name); + try { + field.setAccessible(true); + } catch (Throwable e) { + // Intentionally left empty + } + return field; + } + + /** + * Try to rethrow an exception or create a runtime exception and throw it. + */ + public static RuntimeException rethrow(Throwable e) { + if (e instanceof RuntimeException re) return re; + if (e instanceof Error er) throw er; + return new RuntimeException(e); + } + + /** + * Copy of Node.WVISIST + * Used to clear as exceptions might happen in the walk and left this bitset not cleared + */ + private static final BitSet NodeWalkVisit; + private static final java.util.Random WorkRandom; + + /** + * Write access to Iterate.MID_ASSERT + * Exception could happen mid assert and left this at true. Used to reset to false prior to parsing. + */ + private static final MethodHandle set_MID_ASSERT; + + /** + * Some problems might trigger debug output. Suppress this output with a null print stream during fuzzing. + */ + public static final PrintStream NULL_PRINT_STREAM = new PrintStream(OutputStream.nullOutputStream()); + + static { + try { + NodeWalkVisit = getFieldValue(Node.class, "WVISIT"); + WorkRandom = getFieldValue(getFieldValue(IterPeeps.class, "WORK"), "_R"); + set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT")); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Check that the messages are similar. + */ + private static boolean similarMessages(String a, String b) { + if (a == null || b == null) return true; + a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); + b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); + return a.equals(b); + } + + /** + * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization + * and allow the other to be any stack trace. + */ + private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) { + if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing. + return a[0].equals(b[0]); + } + + /** + * Checks if two exceptions are caused by the same problem. + */ + public static boolean isExceptionFromSameCause(Throwable a, Throwable b) { + if (a == null || b == null) return a == b; // Handle the null case + if (a.getClass() != b.getClass()) return false; // Different classes + if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard + var s1 = a.getStackTrace(); + var s2 = b.getStackTrace(); + if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins + if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes + var o1 = a.getSuppressed(); + var o2 = b.getSuppressed(); + if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression + for (int i=0; i {} + + /** + * Holder for the rewrite rules + */ + private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) { + Replace(Pattern p, Object... replacements) { + this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new)); + } + private static Replacer from(Object o) { + if (o instanceof Replacer r) return r; + if (o instanceof Integer i) return (s, m)->s.append(m.group(i)); + if (o instanceof String r) return (s, m)->s.append(r); + throw new IllegalArgumentException(); + } + } + + /** + * List of rewrite rules. + */ + private static final Replace[] REPLACEMENTS = { + new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"), + new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""), + new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), + new Replace(Pattern.compile("\\b--"), "+"), + new Replace(Pattern.compile("-"), ""), + new Replace(Pattern.compile("\\b\\+-"), "-"), + new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), + new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1), + new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2), + new Replace(Pattern.compile("\\(([^()]+)\\)"), 1), + new Replace(Pattern.compile("\\d+\\+"), ""), + new Replace(Pattern.compile("\\+\\d+"), ""), + new Replace(Pattern.compile("\\n\\n+"), "\n"), + new Replace(Pattern.compile("^\\n+|\\n+$"), ""), + new Replace(Pattern.compile("else *"), ""), + new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "") + }; + + /** + * Predicate to test if the reduced script generates the same error. + */ + private final Predicate tester; + + /** + * Create a reducer with a test function. + * @param tester Test function which returns true when the same issue is found in the script. + */ + private Reducer(Predicate tester) { + this.tester = tester; + } + + /** + * Rewrites the script with one rewrite rule. + */ + private String doReplacement(String script, Pattern pattern, Replacer[] replacements) { + var matcher = pattern.matcher(script); + var sb = new StringBuilder(); + var last = 0; + var skip = 0; + outer: + while (matcher.find(last + skip)) { + sb.append(script, last, matcher.start()); + last = matcher.end(); + skip = last == matcher.start() ? 1 : 0; + var pos = sb.length(); + for (var replacement : replacements) { + replacement.accept(sb, matcher); + var tail = sb.length(); + sb.append(script, last, script.length()); + if (tester.test(sb.toString())) { + sb.setLength(tail); + continue outer; + } + sb.setLength(pos); + } + last = matcher.start(); + skip = 1; + } + sb.append(script, last, script.length()); + return sb.toString(); + } + + /** + * Clean up variable names from the random ones generated by the fuzzer. + */ + private String cleanVariables(String script) { + var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) ([0-9a-zA-Z_]+)").matcher(script); + int[] num = {0, 0}; + while (matcher.find()) { + var type = matcher.group(1); + var var = matcher.group(2); + if (type.equals("new")) continue; + String prefix = "v"; + int t = 0; + if (type.equals("struct")) { + t = 1; + prefix = "s"; + } + if (var.matches(prefix+"\\d+")) continue; + while (script.contains(prefix+num[t])) num[t]++; + var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]); + if (tester.test(n)) { + script = n; + } + } + return script; + } + + /** + * Run all rewrite rules once. + */ + private String doAllReplacements(String script) { + for (var replace: REPLACEMENTS) { + String old; + do { + old = script; + script = doReplacement(script, replace.pattern, replace.replacements); + } while (replace.iterate && !old.equals(script)); + } + return script; + } + + /** + * Reduce the script to a minimal version which produces the same problem with a test function. + * @param tester Test function which returns true when the same issue is found in the script. + */ + public static String reduce(String script, Predicate tester) { + var reducer = new Reducer(tester); + String old; + do { + old = script; + script = reducer.doAllReplacements(script); + script = reducer.cleanVariables(script); + } while (!script.equals(old)); + return script; + } + + /** + * Reduce the script to a minimal version which produces the same problem with a reproducer. + * @param ex The exception originally caused by the script + * @param reproducer Test function which throws an exception which will be checked against ex. + */ + public static String reduce(String script, Throwable ex, Consumer reproducer) { + return reduce(script, s->{ + Throwable nex = null; + try { + reproducer.accept(s); + } catch (Throwable e) { + nex = e; + } + return FuzzerUtils.isExceptionFromSameCause(nex, ex); + }); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java new file mode 100644 index 00000000..155063ab --- /dev/null +++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -0,0 +1,792 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.ScopeNode; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.function.Predicate; + +/** + * Generate a pseudo random script. + * This generator will generate the same script for two random number generators initialized with the same seed. + * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions. + * It is guaranteed to terminate. + */ +public class ScriptGenerator { + + private static final int MAX_STATEMENTS_PER_BLOCK = 5; + private static final int MAX_BINARY_EXPRESSIONS_PER_EXPRESSION = 10; + private static final int MAX_UNARY_EXPRESSIONS_PER_EXPRESSION = 6; + private static final int MAX_NAME_LENGTH = 10; + private static final int MAX_BLOCK_DEPTH = 4; + private static final int MAX_EXPRESSION_DEPTH = 10; + + /** + * Number of spaces per indentation + */ + private static final int INDENTATION = 4; + /** + * Valid characters for identifiers. The last 10 are not valid for the first character. + */ + private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; + /** + * List of keywords not valid for identifiers. + */ + private static final HashSet KEYWORDS; + + static { + try { + KEYWORDS = FuzzerUtils.getFieldValue(new Parser(""), "KEYWORDS"); + } catch (Throwable e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Flag for a statement that it does not pass on control flow to the next statement. + */ + private static final int FLAG_STOP = 0x01; + /** + * Flag for a statement that it contains a final if statement without an else branch. + */ + private static final int FLAG_IF_WITHOUT_ELSE = 0x02; + /** + * Binary operators. + */ + private static final String[] BINARY_OP = {"==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"}; + /** + * Unary operators. + */ + private static final String[] UNARY_OP = {"-"}; + + private static class Type { + final String name; + Type(String name) {this.name=name;} + boolean isa(Type other) { return this == other; } + } + + private static class TypeStruct extends Type { + record Field (String name, Type type) {} + final Field[] fields; + final TypeNullable nullable = new TypeNullable(this); + TypeStruct(String name, Field[] fields) { + super(name); + this.fields = fields; + } + boolean isa(Type other) { return this == other || other == nullable; } + } + + private static class TypeNullable extends Type { + + final Type base; + + TypeNullable(Type base) { + super(base.name+"?"); + this.base = base; + } + + } + + private static final Type TYPE_INT = new Type("int"); + + + private static class Variable { + final String name; + final Type declared; + final Variable shadowing; + Type type; + boolean shadowed = false; + + Variable(String name, Type type, Variable var) { + this.name = name; + this.declared = type; + this.type = type; + this.shadowing = var; + } + } + + /** + * The random number generator used for random decisions. + */ + private final Random random; + /** + * Indentation depth. + */ + private int indentation = 0; + /** + * Output string builder + */ + private final StringBuilder sb; + /** + * Number of nested loops + */ + private int loopDepth = 0; + /** + * Current scope start in the variables list. + */ + private int currScopeStart = 0; + /** + * The depth of blocks allowed. + */ + private int depth = MAX_BLOCK_DEPTH; + /** + * The depth of expressions allowed. + */ + private int exprDepth = MAX_EXPRESSION_DEPTH; + /** + * Current variables in scope. + */ + private final ArrayList variables = new ArrayList<>(); + /** + * All defined structs. + */ + private final ArrayList structs = new ArrayList<>(); + /** + * Allow to declare structs. + */ + private boolean allowStructs = true; + /** + * If this needs to generate valid scripts or is also allowed to generate invalid ones. + */ + private boolean generateValid; + /** + * If this script might be invalid. + */ + private boolean maybeInvalid = false; + + + /** + * + * @param random the random number generator. + * @param sb the script will be written to this string builder. + * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs. + */ + public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) { + this.random = random; + this.sb = sb; + this.generateValid = generateValid; + } + + /** + * Random number with log distribution + * @param max The maximum value + * @return A random number in the range [0, max) with higher numbers being more uncommon. + */ + private int randLog(int max) { + max--; + int n = 1<=0; i--) { + if ((num & (1<2) return false; + maybeInvalid = true; + return true; + } + + /** + * Lookup a variable by name + * @param name name of the variable + * @return The index of the variable in variables or -1 + */ + private int lookup(String name) { + for (int i=variables.size()-1; i>=0; i--) { + if (variables.get(i).name.equals(name)) return i; + } + return -1; + } + + /** + * Lookup a variable by name + * @param name name of the variable + * @return THe variable of null if not found + */ + private Variable lookupVar(String name) { + int idx = lookup(name); + return idx == -1 ? null : variables.get(idx); + } + + /** + * Add a variable to the current scope + * @param name The name of the variable + * @param type The declared type of the variable + */ + private void addVariable(String name, Type type) { + var old = lookupVar(name); + variables.add(new Variable(name, type, old)); + if (old != null) old.shadowed = true; + } + + /** + * Pop variables to level to + * @param to Level until which variables should be popped + */ + private void popVariables(int to) { + while (variables.size() > to) { + var v = variables.removeLast(); + if (v.shadowing != null) v.shadowing.shadowed = false; + } + } + + /** + * Get a random visible variable patching pred + * @param pred The predicate the variable needs to match + * @return A random visible variable matching pred or null if no variable mached pred. + */ + private Variable findVisibleVariablesMatching(Predicate pred) { + int num=0; + for (var v:variables) { + if (!v.shadowed && pred.test(v)) num++; + } + if (num == 0) return null; + num = random.nextInt(num); + for (var v:variables) { + if (!v.shadowed && pred.test(v) && num-- == 0) return v; + } + throw new AssertionError(); + } + + /** + * Get a structure by name + * @param name Name of the structure + * @return The structure of null + */ + private TypeStruct getStruct(String name) { + for (var s:structs) { + if (s.name.equals(name)) return s; + } + return null; + } + + /** + * Generate a variable name. This might be new random name or a name from an outer scope. + * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope. + * @return The variable name. + */ + private String getVarName() { + if (currScopeStart > 0 && random.nextInt(10) > 7) { + // Use a variable outside the current scope. + var v = variables.get(random.nextInt(currScopeStart)); + if (lookup(v.name) < currScopeStart) return v.name; + if (!generateValid) { + maybeInvalid = true; + return v.name; + } + } + // Generate a new random variable name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + /** + * Get a random structure name + * @return A random structure name + */ + private String getStructName() { + if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name; + // Generate a new random struct name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || getStruct(v) != null) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + /** + * Get a random type + * @return A random type + */ + private Type getType() { + if (structs.isEmpty() || random.nextBoolean()) return TYPE_INT; + var idx = random.nextInt(structs.size()); + TypeStruct struct = structs.get(idx); + if (random.nextBoolean()) return struct.nullable; + return struct; + } + + /** + * Generates a program and writes it into the string builder supplied in the constructor. + * @return If the program is allowed to be invalid returns if something potentially invalid was generated. + */ + public boolean genProgram() { + var oldGenerateValid = generateValid; + if (!generateValid && random.nextBoolean()) generateValid = true; + this.maybeInvalid = false; + addVariable(ScopeNode.ARG0, TYPE_INT); + currScopeStart = variables.size(); + if ((genStatements() & FLAG_STOP) == 0) { + if (generateValid || random.nextInt(10)<7) { + genReturn(); + } else { + this.maybeInvalid = true; + } + } + generateValid = oldGenerateValid; + return !this.maybeInvalid; + } + + /** + * Generate a list of statements. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated + */ + public int genStatements() { + var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1)); + for (int i=0; i allowStructs || generateInvalid() ? genStruct() : genAssignment(); + case 1 -> genBlock(); + case 2 -> genIf(); + case 3 -> genCountedLoop(); + case 4 -> genWhile(); + case 5, 6 -> genDecl(); + case 7, 8, 9 -> genAssignment(); + case 10 -> genNullCheck(); + default -> genExit(); + }; + } + + /** + * Generate a new structure + * @return 0 + */ + public int genStruct() { + var name = getStructName(); + var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)]; + sb.append("struct ").append(name).append(" {\n"); + indentation += INDENTATION; + for (int i=0; i { + depth++; + indentation -= INDENTATION; + var ret = genBlock(); + indentation += INDENTATION; + depth--; + yield ret; + } + case 6 -> genIf(); + case 7 -> genCountedLoop(); + case 8 -> genWhile(); + case 9 -> genAssignment(); + case 10 -> genNullCheck(); + default -> genExit(); + }; + depth++; + } + indentation -= INDENTATION; + allowStructs = oldAllowStructs; + return res; + } + + /** + * Generate an exit. This can be return or break and continue when in a loop. + * @return FLAG_STOP + */ + public int genExit() { + if (loopDepth == 0 && generateValid) return genReturn(); + return switch (random.nextInt(7)) { + case 0,1,2 -> { + if (loopDepth == 0) maybeInvalid = true; + sb.append("continue;"); + yield FLAG_STOP; + } + case 3,4,5 -> { + if (loopDepth == 0) maybeInvalid = true; + sb.append("break;"); + yield FLAG_STOP; + } + default -> genReturn(); + }; + } + + /** + * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator. + * @return flag FLAG_STOP for the generated statement + */ + public int genBlock() { + if (depth == 0) { + sb.append("{}"); + return 0; + } + var oldAllowStructs = allowStructs; + var oldCSS = currScopeStart; + allowStructs = false; + currScopeStart = variables.size(); + depth--; + sb.append("{\n"); + indentation += INDENTATION; + var stop = genStatements(); + indentation -= INDENTATION; + printIndentation().append("}"); + popVariables(currScopeStart); + depth++; + currScopeStart = oldCSS; + allowStructs = oldAllowStructs; + return stop & ~FLAG_IF_WITHOUT_ELSE; + } + + /** + * Generate an if statement with an optional else block. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genIf() { + sb.append("if("); + genExpression(TYPE_INT); + sb.append(") "); + var stop = genStatementBlock(); + if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { + sb.append("\n"); + printIndentation().append("else "); + stop &= genStatementBlock(); + } else { + stop = FLAG_IF_WITHOUT_ELSE; + } + return stop; + } + + /** + * Generate a block with a null check + * if (nullable) { handle_nullable_as_non_nullable; } + */ + public int genNullCheck() { + var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable); + if (v == null) return genIf(); + var n = (TypeNullable)v.declared; + boolean negate = random.nextBoolean(); + sb.append("if("); + if (negate) sb.append("!"); + sb.append(v.name).append(")"); + if (!negate) v.type = n.base; + var stop = genStatementBlock(); + v.type = n; + if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { + sb.append("\n"); + printIndentation().append("else "); + if (negate) v.type = n.base; + stop &= genStatementBlock(); + v.type = n; + } else { + stop = FLAG_IF_WITHOUT_ELSE; + } + return stop; + } + + /** + * Generate a while loop. + * @return 0 + */ + public int genWhile() { + sb.append("while("); + genExpression(TYPE_INT); + sb.append(") "); + loopDepth++; + genStatementBlock(); + loopDepth--; + return 0; + } + + /** + * Generate a counted loop of the form + * + * { + * int varname = start_expr; + * while (varname < end_expr) loop_body; + * } + * + * @return 0 + */ + public int genCountedLoop() { + if (depth == 0) return genWhile(); + var oldCSS = currScopeStart; + currScopeStart = variables.size(); + sb.append("{\n"); + indentation += INDENTATION; + String name = getVarName(); + printIndentation().append("int ").append(name).append("="); + genExpression(TYPE_INT); + sb.append(";\n"); + addVariable(name, TYPE_INT); + printIndentation().append("while(").append(name).append("<"); + genExpression(TYPE_INT); + sb.append(") {\n"); + indentation += INDENTATION; + depth--; + loopDepth++; + printIndentation().append(name).append("=").append(name).append("+"); + genExpression(TYPE_INT); + sb.append(";\n"); + genStatements(); + loopDepth--; + depth++; + indentation -= INDENTATION; + printIndentation().append("}\n"); + indentation -= INDENTATION; + printIndentation().append("}"); + popVariables(currScopeStart); + currScopeStart = oldCSS; + return 0; + } + + /** + * Generate a declaration statement. + * @return 0 + */ + public int genDecl() { + var type = getType(); + var name = getVarName(); + sb.append(generateInvalid() ? getRandomName() : type.name).append(" ").append(name); + if (!(type instanceof TypeNullable) || random.nextBoolean()) { + sb.append("="); + genExpression(type); + } + sb.append(";"); + addVariable(name, type); + return 0; + } + + /** + * Generate an assignment statement. + * @return 0 + */ + public int genAssignment() { + Type type; + Type declared; + if (generateInvalid()) { + sb.append(getRandomName()); + type = getType(); + declared = type; + } else { + var v = findVisibleVariablesMatching(va->true); + if (v == null) return genDecl(); + sb.append(v.name); + type = v.type; + declared = v.declared; + } + if (generateInvalid()) { + var name = getRandomName(); + sb.append(".").append(name); + declared = getType(); + } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) { + var field = s.fields[random.nextInt(s.fields.length)]; + sb.append(".").append(field.name); + declared = field.type; + } + sb.append("="); + genExpression(declared); + sb.append(";"); + return 0; + } + + /** + * Generate a return statement. + * @return FLAG_STOP + */ + public int genReturn() { + var type = getType(); + sb.append("return "); + genExpression(type); + sb.append(";"); + return FLAG_STOP; + } + + /** + * Generate a binary expression. + * This method does not care about operator precedence. + */ + public void genExpression(Type type) { + if (generateInvalid()) type = getType(); + if (type != TYPE_INT) { + genUnary(type); + return; + } + var num = randLog(MAX_BINARY_EXPRESSIONS_PER_EXPRESSION); + while(num-->0) { + genUnary(TYPE_INT); + sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); + } + genUnary(TYPE_INT); + } + + /** + * Generate a unary expression. + */ + public void genUnary(Type type) { + if (generateInvalid()) type = getType(); + if (type != TYPE_INT) { + genSuffix(type); + return; + } + var num = randLog(MAX_UNARY_EXPRESSIONS_PER_EXPRESSION); + while(num-->0) { + sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); + } + genSuffix(TYPE_INT); + } + + /** + * Generate + */ + public void genSuffix(Type type) { + if (generateInvalid()) type = getType(); + if (type == TYPE_INT && random.nextBoolean()) { + var t = getType(); + if (generateInvalid()) { + var field = getRandomName(); + genPrimary(t); + sb.append(".").append(field); + return; + } else if (t instanceof TypeStruct s && s.fields.length > 0) { + var field = s.fields[random.nextInt(s.fields.length)]; + if (field.type == TYPE_INT) { + genPrimary(t); + sb.append(".").append(field.name); + return; + } + } + } + genPrimary(type); + } + + /** + * Generate a primary expression. + */ + public void genPrimary(Type type) { + if (generateInvalid()) type = getType(); + var rand = random.nextInt(10); + if (rand == 0 && exprDepth != 0) { + sb.append("("); + exprDepth--; + genExpression(type); + exprDepth++; + sb.append(")"); + } else if (rand < 6) { + genVariable(type); + } else { + genConst(type); + } + } + + /** + * Generate a constant. + */ + public void genConst(Type type) { + if (generateInvalid()) type = getType(); + if (type == TYPE_INT) { + var rand = random.nextInt(10); + switch (rand) { + case 0 -> sb.append("true"); + case 1 -> sb.append("false"); + default -> sb.append(random.nextInt(1<<(rand-2))); + } + } else if (type instanceof TypeNullable n) { + if (random.nextBoolean()) { + sb.append("null"); + } else { + sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name); + } + } else { + sb.append("new ").append(generateInvalid() ? getRandomName() : type.name); + } + } + + /** + * Generate a variable. If there are none generate a number instead. + * If the program is allowed to be invalid a random name can be generated in some cases. + */ + public void genVariable(Type type) { + if (generateInvalid()) { + sb.append(getRandomName()); + return; + } + if (generateInvalid()) type = getType(); + var t = type; + var v = findVisibleVariablesMatching(va->va.type.isa(t)); + if (v == null) genConst(type); + else sb.append(v.name); + } + +} diff --git a/chapter10a/Makefile b/chapter10a/Makefile deleted file mode 100644 index 5ed21084..00000000 --- a/chapter10a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# Standalone compiler snapshot; shared rules also work in linearized checkouts. -include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) -test_cp += com.seaofnodes.simple.FuzzerWrap - -.PHONY: fuzzer -fuzzer: $(test_classes) - java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter10a/README.md b/chapter10a/README.md deleted file mode 100644 index e150c664..00000000 --- a/chapter10a/README.md +++ /dev/null @@ -1,424 +0,0 @@ -# Chapter 10a: Structs and Memory - -[Previous: Chapter 9](../chapter09/README.md) | -[Next: Chapter 10b](../chapter10b/README.md) - -This chapter adds user-defined structs, pointers, allocation, field loads and -stores, and null checking. It also introduces an evaluator that executes the -graph, including its memory effects. The [grammar](docs/10-grammar.md) is the -same in 10a and 10b; the next chapter improves the memory representation. - -## References - -Structs are a collection of named and typed fields; very classically similar to -C or Java. New structs are made with the `new` keyword; this allocates memory -and produces a reference. Freeing memory awaits a later chapter. Fields are -accessed with the `.` notation. The fields can hold any scalar value, -including reference other structs and be mutually recursive. Any reference -field can allow null or not, same as a normal variable. - -```java -struct Person { - String last; // Last name, must exist - String? middle; // Optional middle name - int age; -}; -``` - -Here we have fields `last`, a not-null final String, and `middle` a nullable -and final String, and an `int age`. Like all `int` declared fields, it is -mutable. Later chapters will allow both mutable reference fields and immutable -integer fields; for now this choice covers most of the common ground. - -You have to have a reference to get at the fields, static fields are added in a later chapter. - -All field references have to be against not-null pointers, and this is directly -checked in the type system. I.e., Simple disallows null-pointer-exceptions by -design. Some examples of null checking before using: - -```java -print(ptr.age); // ERROR! ptr might be null - -if( ptr ) print(ptr.age); // Test before use - -// Field use can be far removed, as long as the check dominates -if( ptr ) { - S1; // Other statements - while( other ) { ...ptr.age... } -}; -``` - -Allocation, as well as Stores, means we now have memory effects to deal with. - - -## One memory value - -Arithmetic nodes depend on the values they use, similarly a load also depends -on when it observes memory. In this chapter every store consumes the preceding -memory state and produces the next one, and a load consumes memory without -changing it. This makes the source program's memory ordering explicit in the -graph. - -```java -struct Point { int x; int y; } -Point p = new Point; -p.x = 3; -p.y = arg; -return p.x; -``` - -`NewNode` produces the pointer. The parser emits stores of zero for both fields, -then stores 3 into `x` and `arg` into `y`. The final load observes the memory -after the `y` store. We deliberately keep that dependency here, even though -different fields are independent. Chapter 10b will remove it. - -![One chain orders stores and loads](docs/example1.svg) - -The graphs show dependencies before local folding. Definitions appear above -their uses, except for loop backedges; arrows point from use to definition. -To focus on memory, these illustrations show only small, detached True/False -control projections gating Loads and Stores. These schematic gates indicate -the source branches; Chapter 11 chooses the actual scheduling inputs. Other -control-flow context, including the Regions that bind Phis, is omitted. -The layout shows dependencies, not an execution schedule. - -| Node | Inputs | Result | -|----------|---|---| -| `New` | Control and a struct type | A fresh, non-null object pointer | -| `Store` | Memory, pointer, field name, value | The updated whole-memory state | -| `Load` | Memory, pointer, field name | The value of the field | -| `Start` | External entry | Control, initial memory, and `arg` | -| `Return` | Control, memory, result | Completion with all preceding effects retained | - -For nodes consuming or producing both control and memory, control occupies slot -0 and memory slot 1. Start's outputs are `{ctrl, $mem, arg}`; Return's inputs -are `{ctrl, $mem, result}`. - -The memory edge is a dependency, not a copy of the heap. The graph determines -the legal execution order. In hardware, a Store updates physical memory -directly. In the evaluator, a Store updates an object's attached memory array. - -## Memory is an SSA variable - -The parser keeps one hidden binding named `$mem`. Source identifiers cannot -contain `$`, so user code cannot name it. Reading and updating memory uses the -same `ScopeNode` lookup and update operations as a local variable. - -For an `if`, the two branches can finish with different memory values. An -ordinary Phi selects the memory from the branch that executed: - -```java -struct Point { int x; int y; } -Point p = new Point; -if( arg ) p.x = 3; else p.x = 4; -return p.x; -``` - -![Whole memory across a branch](docs/example2a.svg) - -Loops reuse Chapter 8's lazy Phi construction. Reading or updating `$mem` in a -loop creates an initially incomplete memory Phi. Closing the loop supplies its -backedge. `break`, `continue`, nested loops, and early returns use the same scope -mechanics as scalar variables. - -```java -struct Point { int x; int y; } -Point p = new Point; -while( arg ) { - p.x = arg; - arg = arg-1; -}; -return p.x; -``` - -![Whole memory around a loop](docs/example2b.svg) - -Every Return consumes the current whole-memory value. This keeps stores alive -even when the returned scalar does not depend on them, or when the program -returns the modified object itself. - -## Enhanced Type Lattice - -The Type Lattice for Simple has a major revision in this chapter. - -![Type lattice with six domains and parallel named struct alternatives](./docs/lattice.svg) - -The diagram uses `⊤` for **top** and `⊥` for **bottom**. A suffix names the -domain: `⊤:struct` is *struct top*, while the bare `⊤` is global top. In the -prose we use those words; in code examples we use the actual Java constants. -The implementation's `$TOP` and `$BOT` names need not appear in the diagram: - -| Diagram | Prose | Java constant | Internal struct name | -|---|---|---|---| -| `⊤:struct` | struct top | `TypeStruct.TOP` | `$TOP` | -| `⊥:struct` | struct bottom | `TypeStruct.BOT` | `$BOT` | - -Edges in this diagram show lattice order, with top above bottom. Boxes -containing an ellipsis abbreviate further parallel alternatives; they are not -extra union types. Field and tuple-element type lattices are elided in the -diagram. Field types can be any scalar type (Integer or Pointer), Tuple types -can be *any* lattice type, including Control and Memory. - -Within the type lattice, we now have the following domains: - -* ![Control](docs/type-control.svg) represents control flow: unreachable control - at control top, and reachable control at control bottom. -* ![Integer](docs/type-integer.svg) has integer top, integer bottom, and parallel - constants between them. The ellipses stand for the other integer constants. -* ![Pointer](docs/type-pointer.svg) (new) combines a struct type with nullability. - * The prefix `*` means pointer-to: `*Person` is a non-null pointer to a Person. - The suffix `?` includes null, so `*Person?` permits a Person pointer or null. - * The pointer domain contains the whole struct lattice twice, once without - null and once with it. The diagram expands named alternatives in both - copies. It is not just two individual pointer types. - * `null` combines struct top with the nullable flag: it has no non-null - object alternative. The diagram labels it `*⊤:struct?` as well as `null`. - * A pointer to struct bottom can point to any struct. The diagram shows the - non-null form as `*⊥:struct` and the nullable form as `*⊥:struct?`. - The latter is pointer bottom; pointer top is `*⊤:struct`. - * Null evaluates to false; non-null pointers evaluate to true. - - > C analogy: the non-null pointer to struct bottom is akin to `void *`, except - > that C's `void *` may also be null. Simple names this constant - > `TypeMemPtr.VOIDPTR` and prints it as `*void`. Struct bottom itself is the - > common lower bound of struct types, not C's `void` type. - -* ![Struct](docs/type-struct.svg) (new) represents user-defined named structs. - Fields have integer types in this chapter. - * Struct top lies above every named struct; struct bottom lies below every - named struct. Top is the high/empty struct alternative, while bottom - conservatively includes any struct name. - * `Person`, `s1`, `s2`, and further names are parallel alternatives. There is - no inheritance, so different named structs do not nest beneath one another, - even if they have identical fields. - * There is no fixed bound on the names a program can declare. The diagram's - ellipsis stands for more named alternatives, not a distinguished unnamed - struct. Each named box abbreviates its own field-type lattice. -* ![Memory](docs/type-memory.svg) (new) represents the whole-memory token. - Memory top and memory bottom are `TypeMem.TOP` and `TypeMem.BOT`. Neither - records the values stored in individual fields. -* ![Tuple](docs/type-tuple.svg) represents a collection of results from one node, - such as Start's control, memory, and argument. The pink headers identify tuples - of different lengths; each vertically stacked black `type` cell stands for - the full type lattice again, including the possibility of another tuple. - -We use the following operations on the lattice: - -* **Meet** computes the greatest lower bound. For these examples, think of - combining the alternatives represented by its inputs. - * The meet of `Person` and `s1` is struct bottom. There is no separate type - representing exactly those two names. - * The meet of `*Person` and `*s1` is the non-null pointer to struct bottom. - * The meet of `*Person` and `null` is `*Person?`. - * The meet of the non-null pointer to struct bottom and `null` is pointer bottom. - * The meet of `1` and `2` is integer bottom. -* **Dual** reflects the lattice about its centerline: top and bottom exchange - places, and applying dual twice returns the original type. - * Struct top and struct bottom are duals. - * `null` and the non-null pointer to struct bottom are duals. - * A pointer's dual also duals its struct's fields and flips nullability. Thus - the dual of `*Person` is not generally the original `*Person?`: the field - types must be dualed too. Those field details are omitted in the diagram. -* **Join** computes the least upper bound. It is defined through meet and dual: - `JOIN(x,y) = MEET(x.dual(),y.dual()).dual()`. - * The join of `Person` and `s1` is struct top. - * The join of `*Person` and `*s1` is pointer top. - * The join of the non-null pointer to struct bottom and `null` is pointer top. - * The join of `1` and `2` is integer top. - -As we construct the Sea of Nodes graph, we ensure that values stay inside the -domain they are created in - that is pointer fields will never contain integers -(obviously)... nor even the generic `TOP` and `BOT`. There are a couple of -nuances worth highlighting. - -* When Phis are created, the initial type of the Phi is based on the declared - type of its first input. This is a pessimistic type assignment because we do - not yet know what other types will be met. - -* When all the inputs of the Phi are known, we start with the local Top of the - declared type, and then compute a meet of all the input types. This - computation results in a more refined type for the Phi. - -## Parser Enhancements - -In previous chapters the only available type was Integer. Now, variables can -be of Integer type or pointer to Struct types. To support this, the Parser now -tracks the declared type of a variable (used to only be Integer!). The -declared type of a variable defines the set of values that can be legitimately -assigned to the variable. The Sea of Nodes graph also tracks the actual type -of values assigned to variables, these type transitions are defined by the Type -lattice described in the previous section. - -## Null Pointer Analysis - -The Simple language syntax allows a pointer variable to be specified as -null-able - and it prevents ever throwing a Null Pointer Exception. The -compiler decides if a null check is needed, and if needed and missing -the compiler rejects the program (asking the user to insert a null check). - -When the Parser encounters conditions that test the truthiness of a pointer variable, it uses this knowledge to refine the type information in the branches that follow. - -Here are two motivating examples: - -```java -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -if( bar ) bar.a = 1; -``` - -If `bar` is not `null` above, then we would like to allow the assignment to -`bar.a`. If the final line lacked the null check, e.g. a plain `bar.a = 1;`, -then `Java` would compile this program but throw a NPE, and Simple rejects the -program pointing out a null check is required. - - -```java -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -int rez = 3; -if( !bar ) rez=4; -else bar.a = 1; -``` - -If `bar` is `null` then we would like `!bar` to evaluate to `true`. In the else -branch we know `bar` is not null, hence the assignment to `bar.a` should be -allowed. - -To enable this behaviour, we make the following enhancements. - -* We track the null-ness of a pointer in the Type system, and hence in every - Node that ptr type flows through. - -* We "wrap" the predicate of an `If` node with an up-cast when we see that the - predicate is a ptr value. The up-cast removes the null possibility on the - true branch making it legal to use the pointer. - -* The up-cast uses a `Cast` with the non-null pointer to struct bottom - (`TypeMemPtr.VOIDPTR`). It joins the original pointer type with that type, - corresponding to `*⊥:struct` in the diagram. This JOIN operation preserves - everything we already know about ptr, and also adds the new knowledge that - the ptr is not-null. - -* The up-cast is represented by the `CastNode` op, and if applicable, we - replace all occurrences of the original predicate with the up-cast in the - current scope. The change is local to each branch of the `If`; occurring - separately for the true and the false branches of the `If` (and the false - branch inverts the predicate). - -* The up-cast peepholes normally; if its input's type is a super type of the - up-cast, then the `CastNode` can be removed. See `ScopeNode.upcast()` method, - and `CastNode`. - -* When computing a type for Not, we produce an Integer type when its input is a - ptr. When the input is a `null` we convert to `1` and when it is a non-null - ptr value, we convert to `0`. See `NotNode.compute()`. - - -## Local memory optimizations - -A load immediately following a store to the **same pointer and field** can use -the stored value. We just wrote `arg` into `p.x`, so reading it back needs no -memory access: - -```java -struct Box { int x; } -Box p = new Box; -p.x = arg; -return p.x; // Can return arg directly. -``` - -| Before: read back the stored value | After: use the value directly | -|---|---| -| ![Before: Return reads Load p.x after Store p.x](docs/load-after-store-before.svg) | ![After: Return uses arg and still consumes the Store's memory](docs/load-after-store-after.svg) | - -Return's result input changes from the Load to `arg`, and the unused Load -disappears. Return still consumes the Store's memory, so the write is retained. - -A store immediately following another store to the same address can also -discard the earlier store when no other user needs it. That separate rewrite -can remove the zero-initialization store still shown above. Both the pointer -and field checks matter: two different fields of one object are different -addresses, as are the same fields of two different objects. - -A load can also move up through a memory Phi. This extends -[Chapter 5's push-through-Phi optimization](../chapter05/README.md#pushing-addition-up-through-a-phi): -there, arithmetic folds on the incoming paths; here, a load can fold against -a matching store. In graph notation, keeping the same pointer and field: - -```text -Load(Phi(r, m0, m1), p.x) - -> Phi(r, Load(m0, p.x), Load(m1, p.x)) -``` - -The Phi of memory becomes a Phi of loaded values. This chapter handles two -incoming paths and requires a load-after-store fold on at least one of them. -On a loop, that path must be the **backedge**: folding only on entry would -leave a load that could be pushed around the cycle indefinitely. As with the -arithmetic rewrite, exposing a fold is part of deciding to transform the -graph, not just a hoped-for cleanup afterward. See -[`LoadNode.idealize()`](src/main/java/com/seaofnodes/simple/node/LoadNode.java). - -The reverse direction extends -[Chapter 5's common-operation pull-down](../chapter05/README.md#example-3): -a Phi of matching Loads or Stores can become one operation after the join. -For example, these two writes: - -```java -if (arg) s.x = arg+1; -else s.x = arg+2; -``` - -can become `s.x = Phi(arg+1,arg+2)`. The general rewrite creates a separate, -correctly typed Phi for each differing operand, including memory and pointers. -We must be more careful here with memory ops; it is possible to lose some -type precision, so we check for that as part of the profitability. - -Also, a Store must have no other users, so moving it removes the original -effect. A Load must have no possible conflicting writes or memory merges among -its memory's users; otherwise delaying the read could change its value. Much -later, we will be adding anti-dependences, but for now we do a conservative -check. - -### Missing an easy one - -The single memory chain is correct but conservative. A store to `y` can -prevent forwarding an earlier store to `x`, even when the objects are -identical. This is a missed optimization and it motivates the next chapter. - -## Executing the graph - -The evaluator includes a small late scheduler. A load must execute before a -later store that consumes the same memory definition, even though there is no -ordinary data edge from the load to that store. These load/store -anti-dependencies supplement the explicit graph edges. - -For example, after allocating `p`, `int old = p.x; p.x = arg; return old;` -reads the value before the final store. In the graph below, Load and Store -consume the same memory state. The scheduler must execute the Load first; -there is no ordinary data edge between them. - -![A load must precede a clobbering store](docs/example2c.svg) - -The scheduler is sufficient to execute this chapter. Chapter 11 develops -global code motion and scheduling as a separate topic. - -## Build and test - -From this directory, with JDK 21 or newer, Make, and a shell: - -```sh -make lib -make tests -make release -make view -``` - -Make builds this snapshot directly, without Maven. `make release` produces -`build/release/simple.jar`; `make view` launches the shared interactive graph -viewer. Maven and IDEA module descriptions are also supplied. Tests include all -earlier chapters, memory ordering, returned object contents, and nested loops. - -Next, [Chapter 10b](../chapter10b/README.md) keeps this one parser binding while -splitting the graph into independent memory slices. diff --git a/chapter10a/chapter10a.iml b/chapter10a/chapter10a.iml deleted file mode 100644 index a58e898f..00000000 --- a/chapter10a/chapter10a.iml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/chapter10a/docs/10-grammar.md b/chapter10a/docs/10-grammar.md deleted file mode 100644 index 5b9b680e..00000000 --- a/chapter10a/docs/10-grammar.md +++ /dev/null @@ -1,125 +0,0 @@ -# Grammar for Chapter 10a - -```antlrv4 -grammar SimpleLanguage; - -program - : statement+ EOF - ; - -statement - : returnStatement - | structDeclaration - | declStatement - | blockStatment - | expressionStatement - | ifStatement - | whileStatement - | breakStatement - | continueStatment - ; - -field - : 'int' IDENTIFIER ';' - ; - -fields - : field+ - ; - -structDeclaration - : 'struct' IDENTIFIER '{' fields '}' - ; - -whileStatement - : 'while' '(' expression ')' statement - ; - -breakStatement - : 'break' ';' - ; - -continueStatement - : 'continue' ';' - ; - -ifStatement - : 'if' '(' expression ')' statement ('else' statement)? - ; - - -expressionStatement - : IDENTIFIER '=' expression ';' - | fieldExpression '=' expression ';' - ; - -blockStatement - : '{' statement+ '}' - ; - -structName - : IDENTIFIER - ; - -declStatement - : 'int' IDENTIFIER '=' expression ';' - | structName ('?')? IDENTIFIER '=' expression ';' - ; - -returnStatement - : 'return' expression ';' - ; - -expression - : comparisonExpression - ; - -comparisonExpression - : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* - ; - -additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* - ; - -multiplicativeExpression - : unaryExpression (('*' | '/') unaryExpression)* - ; - -unaryExpression - : ('-') unaryExpression - | '!' unaryExpression - | primaryExpression - ; - -newExpression - : 'new' IDENTIFIER - ; - -fieldExpression - : primaryExpresson '.' IDENTIFIER - ; - -primaryExpression - : IDENTIFIER - | INTEGER_LITERAL - | 'true' - | 'false' - | 'null' - | newExpression - | '(' expression ')' - | fieldExpression - ; - -INTEGER_LITERAL - : [1-9][0-9]* - | [0] - ; - -IDENTIFIER - : NON_DIGIT (NON_DIGIT | DIGIT)* - ; - -NON_DIGIT: [a-zA-Z_]; -DEC_DIGIT: [0-9]; -``` diff --git a/chapter10a/docs/Makefile b/chapter10a/docs/Makefile deleted file mode 100644 index 6fa0f057..00000000 --- a/chapter10a/docs/Makefile +++ /dev/null @@ -1 +0,0 @@ -include ../../graph/docs.mk diff --git a/chapter10a/docs/example1.gv b/chapter10a/docs/example1.gv deleted file mode 100644 index 9585b8e4..00000000 --- a/chapter10a/docs/example1.gv +++ /dev/null @@ -1,16 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - M0 [label="Initial memory" shape=octagon style="filled" fillcolor="#dcecff"]; - X [label="Store p.x = 3" shape=octagon style="filled" fillcolor="#dcecff"]; - Y [label="Store p.y = arg" shape=octagon style="filled" fillcolor="#dcecff"]; - L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - L -> Y -> X -> M0 [color="#2867b2"]; - Return -> Y [color="#2867b2"]; Return -> L; - Return [shape=box style="filled" fillcolor="#fff1c4"]; -} diff --git a/chapter10a/docs/example1.json b/chapter10a/docs/example1.json deleted file mode 100644 index 07e1dfef..00000000 --- a/chapter10a/docs/example1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10a/docs/example1.svg", - "step": 1, - "roots": [13], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 11, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 12, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/example1.svg b/chapter10a/docs/example1.svg deleted file mode 100644 index d98e0ccd..00000000 --- a/chapter10a/docs/example1.svg +++ /dev/null @@ -1,118 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[0] → #1 · ASSOC#10[1] → #8 · MEM#10[2] → #5 · DATA#10[3] → #9 · DATA#11[1] → #10 · MEM#11[2] → #5 · DATA#11[3] → #4 · DATA#12[1] → #11 · MEM#12[2] → #5 · DATA#13[1] → #11 · MEM#13[2] → #12 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8#8 Store p.y -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #6 DATA0123#3  #9#9 #3 -Type not computed - -[0] → #1 ASSOC0Store p.x  #10#10 Store p.x -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → #9 DATA0123Store p.y  #11#11 Store p.y -Type not computed - -[0] → — CTRL -[1] → #10 MEM -[2] → #5 DATA -[3] → #4 DATA0123Load p.x  #12#12 Load p.x -Type not computed - -[0] → — CTRL -[1] → #11 MEM -[2] → #5 DATA -[3] → — DATA0123Return  #13#13 Return -Type not computed - -[0] → — CTRL -[1] → #11 MEM -[2] → #12 DATA012 diff --git a/chapter10a/docs/example2a.gv b/chapter10a/docs/example2a.gv deleted file mode 100644 index 93a8a904..00000000 --- a/chapter10a/docs/example2a.gv +++ /dev/null @@ -1,17 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - M [label="Incoming memory" shape=octagon style="filled" fillcolor="#dcecff"]; - A [label="True: Store x" shape=octagon style="filled" fillcolor="#dcecff"]; - B [label="False: Store x" shape=octagon style="filled" fillcolor="#dcecff"]; - P [label="Phi(memory)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; - P -> A -> M [color="#2867b2"]; P -> B -> M [color="#2867b2"]; - P -> Region [style=dashed,color="#9396a3"]; Return -> P [color="#2867b2"]; - Return [shape=box style="filled" fillcolor="#fff1c4"]; - Region [shape=trapezium style="filled" fillcolor="#fff1c4"]; -} diff --git a/chapter10a/docs/example2a.json b/chapter10a/docs/example2a.json deleted file mode 100644 index 7c22da0a..00000000 --- a/chapter10a/docs/example2a.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10a/docs/example2a.svg", - "step": 1, - "roots": [19], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 11, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, - {"id": 12, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 13, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 10, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 12, "role": "DATA", "label": null}], "proj": null}, - {"id": 14, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 11, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 14, "role": "DATA", "label": null}], "proj": null}, - {"id": 17, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 13, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null}, - {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/example2a.svg b/chapter10a/docs/example2a.svg deleted file mode 100644 index b7664907..00000000 --- a/chapter10a/docs/example2a.svg +++ /dev/null @@ -1,130 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#12[0] → #1 · ASSOC#13[0] → #10 · CTRL#13[1] → #8 · MEM#13[2] → #5 · DATA#13[3] → #12 · DATA#14[0] → #1 · ASSOC#15[0] → #11 · CTRL#15[1] → #8 · MEM#15[2] → #5 · DATA#15[3] → #14 · DATA#17[1] → #13 · MEM#17[2] → #15 · MEM#18[1] → #17 · MEM#18[2] → #5 · DATA#19[1] → #17 · MEM#19[2] → #18 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8#8 Store p.y -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #6 DATA0123True  #10/0#10 True -Projection 0 of #0 -Type not computed - -False  #11/1#11 False -Projection 1 of #0 -Type not computed - -#3  #12#12 #3 -Type not computed - -[0] → #1 ASSOC0Store p.x  #13#13 Store p.x -Type not computed - -[0] → #10 CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → #12 DATA0123#4  #14#14 #4 -Type not computed - -[0] → #1 ASSOC0Store p.x  #15#15 Store p.x -Type not computed - -[0] → #11 CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → #14 DATA0123Phi $mem  #17#17 Phi $mem -Type not computed - -[0] → — ASSOC -[1] → #13 MEM -[2] → #15 MEM012Load p.x  #18#18 Load p.x -Type not computed - -[0] → — CTRL -[1] → #17 MEM -[2] → #5 DATA -[3] → — DATA0123Return  #19#19 Return -Type not computed - -[0] → — CTRL -[1] → #17 MEM -[2] → #18 DATA012 diff --git a/chapter10a/docs/example2b.gv b/chapter10a/docs/example2b.gv deleted file mode 100644 index c7d64509..00000000 --- a/chapter10a/docs/example2b.gv +++ /dev/null @@ -1,19 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - P [label="Phi(memory)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; - S [label="Loop-body Store" shape=octagon style="filled" fillcolor="#dcecff"]; - P -> Entry [color="#2867b2"]; - P -> S [color="#2867b2",constraint=false]; // Backedge must not place Store before Phi. - S -> P [color="#2867b2"]; - P -> Loop [style=dashed,color="#9396a3"]; Return -> P [color="#2867b2"]; - Return -> S [style=invis]; // Layout only: Phi, Store, Return from left to right. - Return [shape=box style="filled" fillcolor="#fff1c4"]; - Entry [shape=octagon style="filled" fillcolor="#dcecff"]; - Loop [shape=box style="rounded,filled" fillcolor="#ffe0ab"]; -} diff --git a/chapter10a/docs/example2b.json b/chapter10a/docs/example2b.json deleted file mode 100644 index 0b778aea..00000000 --- a/chapter10a/docs/example2b.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10a/docs/example2b.svg", - "step": 1, - "roots": [19], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null}, - {"id": 11, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 17, "role": "DATA", "label": null}], "proj": null}, - {"id": 13, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 14, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, - {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 13, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, - {"id": 16, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 17, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "DATA", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, - {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 14, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/example2b.svg b/chapter10a/docs/example2b.svg deleted file mode 100644 index 1feb91a3..00000000 --- a/chapter10a/docs/example2b.svg +++ /dev/null @@ -1,135 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#10[1] → #8 · MEM#10[2] → #15 · MEM#11[1] → #4 · DATA#11[2] → #17 · DATA#15[0] → #13 · CTRL#15[1] → #10 · MEM#15[2] → #5 · DATA#15[3] → #11 · DATA#16[0] → #1 · ASSOC#17[1] → #11 · DATA#17[2] → #16 · DATA#18[0] → #14 · CTRL#18[1] → #10 · MEM#18[2] → #5 · DATA#19[1] → #10 · MEM#19[2] → #18 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8#8 Store p.y -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #6 DATA0123Phi $mem  #10#10 Phi $mem -Type not computed - -[0] → — ASSOC -[1] → #8 MEM -[2] → #15 MEM012Phi arg  #11#11 Phi arg -Type not computed - -[0] → — ASSOC -[1] → #4 DATA -[2] → #17 DATA012True  #13/0#13 True -Projection 0 of #0 -Type not computed - -False  #14/1#14 False -Projection 1 of #0 -Type not computed - -Store p.x  #15#15 Store p.x -Type not computed - -[0] → #13 CTRL -[1] → #10 MEM -[2] → #5 DATA -[3] → #11 DATA0123#1  #16#16 #1 -Type not computed - -[0] → #1 ASSOC0-  #17#17 - -Type not computed - -[0] → — CTRL -[1] → #11 DATA -[2] → #16 DATA012Load p.x  #18#18 Load p.x -Type not computed - -[0] → #14 CTRL -[1] → #10 MEM -[2] → #5 DATA -[3] → — DATA0123Return  #19#19 Return -Type not computed - -[0] → — CTRL -[1] → #10 MEM -[2] → #18 DATA012 diff --git a/chapter10a/docs/example2c.gv b/chapter10a/docs/example2c.gv deleted file mode 100644 index f3a52b4d..00000000 --- a/chapter10a/docs/example2c.gv +++ /dev/null @@ -1,14 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - M [label="Prior memory" shape=octagon style="filled" fillcolor="#dcecff"]; - L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - S [label="Store p.x" shape=octagon style="filled" fillcolor="#dcecff"]; - L -> M [color="#2867b2"]; S -> M [color="#2867b2"]; - S -> L [style=dashed,xlabel="execute load first"]; -} diff --git a/chapter10a/docs/example2c.json b/chapter10a/docs/example2c.json deleted file mode 100644 index 84c3d2b6..00000000 --- a/chapter10a/docs/example2c.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10a/docs/example2c.svg", - "step": 1, - "roots": [11], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/example2c.svg b/chapter10a/docs/example2c.svg deleted file mode 100644 index f7f7bce3..00000000 --- a/chapter10a/docs/example2c.svg +++ /dev/null @@ -1,109 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #8 · MEM#9[2] → #5 · DATA#10[1] → #8 · MEM#10[2] → #5 · DATA#10[3] → #4 · DATA#11[1] → #10 · MEM#11[2] → #9 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8#8 Store p.y -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #6 DATA0123Load p.x  #9#9 Load p.x -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → — DATA0123Store p.x  #10#10 Store p.x -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → #4 DATA0123Return  #11#11 Return -Type not computed - -[0] → — CTRL -[1] → #10 MEM -[2] → #9 DATA012 diff --git a/chapter10a/docs/lattice.gv b/chapter10a/docs/lattice.gv deleted file mode 100644 index b56a3073..00000000 --- a/chapter10a/docs/lattice.gv +++ /dev/null @@ -1,127 +0,0 @@ -digraph Lattice { - rankdir=TB; - newrank=true; - // Preserve the same-rank ordering edges through cluster layout. - remincross=false; - nodesep=0.22; - ranksep=0.65; - node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; - edge [color="#555555", arrowsize=0.6]; - - // These arrows are lattice-order edges, not use/definition edges. - TOP [label="⊤", fillcolor=black, fontcolor=white]; - BOT [label="⊥", fillcolor=black, fontcolor=white]; - - subgraph cluster_control { - label="Control"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffff00"]; - XCTRL [label="⊤:ctrl"]; - CTRL [label="⊥:ctrl"]; - XCTRL -> CTRL; - } - - subgraph cluster_integer { - label="Integer"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#add8e6"]; - INT_TOP [label="⊤:int"]; - INT_BOT [label="⊥:int"]; - INT_NEG [label="… −2, −1"]; - INT_ZERO [label="0"]; - INT_POS [label="1, 2, …"]; - {rank=same; INT_NEG; INT_ZERO; INT_POS;} - INT_NEG -> INT_ZERO -> INT_POS [style=invis]; - INT_TOP -> {INT_NEG INT_ZERO INT_POS}; - {INT_NEG INT_ZERO INT_POS} -> INT_BOT; - } - - subgraph cluster_pointer { - label="Pointer = nullability × struct type"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#008000", fontcolor=white]; - PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; - PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; - PTR_NULL [label="null"]; - PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; - PTR_PERSON [label="*Person"]; - PTR_S1 [label="*s1"]; - PTR_MORE [label="*s2, …"]; - PTR_PERSON_NIL [label="*Person?"]; - PTR_S1_NIL [label="*s1?"]; - PTR_MORE_NIL [label="*s2?, …"]; - {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} - {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} - PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; - PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; - PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; - {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; - PTR_PERSON -> PTR_PERSON_NIL; - PTR_S1 -> PTR_S1_NIL; - PTR_MORE -> PTR_MORE_NIL; - PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; - {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; - } - - subgraph cluster_struct { - label="Struct\nnamed collection of named scalar types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#90ee90"]; - STRUCT_TOP [label="⊤:struct\n[struct top]"]; - STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; - STRUCT_PERSON [label="Person\nage: int", shape=box]; - STRUCT_S1 [label="s1\nx: int", shape=box]; - STRUCT_S2 [label="s2\nx: int, y: int", shape=box]; - STRUCT_MORE [label="…", shape=box]; - {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} - STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; - STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; - {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; - } - - subgraph cluster_memory { - label="Memory"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#00008b", fontcolor=white]; - MEM_TOP [label="⊤:mem"]; - MEM_BOT [label="⊥:mem"]; - MEM_TOP -> MEM_BOT; - } - - subgraph cluster_tuple { - label="Tuple\ncollection of types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffc0cb"]; - TUPLE_TOP [label="⊤:tuple"]; - TUPLE_BOT [label="⊥:tuple"]; - // Each black element stands for a recursively nested type lattice. - TUPLE1 [shape=plain, label=< - - - -
tuple
type
- >]; - TUPLE2 [shape=plain, label=< - - - - -
tuple
type
type
- >]; - TUPLEN [shape=plain, label=< - - - - - -
tuple
type
…
type
- >]; - {rank=same; TUPLE1; TUPLE2; TUPLEN;} - TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; - TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; - {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; - } - - TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; - {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; - {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} - {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} - XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; -} diff --git a/chapter10a/docs/lattice.svg b/chapter10a/docs/lattice.svg deleted file mode 100644 index 006ab762..00000000 --- a/chapter10a/docs/lattice.svg +++ /dev/null @@ -1,593 +0,0 @@ - - - - - - -Lattice - - -cluster_control - -Control - - -cluster_integer - -Integer - - -cluster_pointer - -Pointer = nullability × struct type - - -cluster_struct - -Struct -named collection of named scalar types - - -cluster_memory - -Memory - - -cluster_tuple - -Tuple -collection of types - - - -TOP - -⊤ - - - -XCTRL - -⊤:ctrl - - - -TOP->XCTRL - - - - - -INT_TOP - -⊤:int - - - -TOP->INT_TOP - - - - - -PTR_TOP - -⊤:ptr -[*⊤:struct] - - - -TOP->PTR_TOP - - - - - -STRUCT_TOP - -⊤:struct -[struct top] - - - -TOP->STRUCT_TOP - - - - - -MEM_TOP - -⊤:mem - - - -TOP->MEM_TOP - - - - - -TUPLE_TOP - -⊤:tuple - - - -TOP->TUPLE_TOP - - - - - -BOT - -⊥ - - - -CTRL - -⊥:ctrl - - - -XCTRL->CTRL - - - - - - -CTRL->BOT - - - - - -INT_NEG - -… −2, −1 - - - -INT_TOP->INT_NEG - - - - - -INT_ZERO - -0 - - - -INT_TOP->INT_ZERO - - - - - -INT_POS - -1, 2, … - - - -INT_TOP->INT_POS - - - - - - -INT_BOT - -⊥:int - - - -INT_BOT->BOT - - - - - -INT_NEG->INT_BOT - - - - - - -INT_ZERO->INT_BOT - - - - - - -INT_POS->INT_BOT - - - - - -PTR_NULL - -null - - - -PTR_TOP->PTR_NULL - - - - - -PTR_PERSON - -*Person - - - -PTR_TOP->PTR_PERSON - - - - - -PTR_S1 - -*s1 - - - -PTR_TOP->PTR_S1 - - - - - -PTR_MORE - -*s2, … - - - -PTR_TOP->PTR_MORE - - - - - - -PTR_BOT - -⊥:ptr -[*⊥:struct?] - - - -PTR_BOT->BOT - - - - - -PTR_PERSON_NIL - -*Person? - - - -PTR_NULL->PTR_PERSON_NIL - - - - - -PTR_S1_NIL - -*s1? - - - -PTR_NULL->PTR_S1_NIL - - - - - -PTR_MORE_NIL - -*s2?, … - - - -PTR_NULL->PTR_MORE_NIL - - - - - -PTR_ANY - -*⊥:struct -[ptr any struct] - - - -PTR_ANY->PTR_BOT - - - - - - -PTR_PERSON->PTR_ANY - - - - - - -PTR_PERSON->PTR_PERSON_NIL - - - - - -PTR_S1->PTR_ANY - - - - - - -PTR_S1->PTR_S1_NIL - - - - - - -PTR_MORE->PTR_ANY - - - - - -PTR_MORE->PTR_MORE_NIL - - - - - -PTR_PERSON_NIL->PTR_BOT - - - - - - -PTR_S1_NIL->PTR_BOT - - - - - - -PTR_MORE_NIL->PTR_BOT - - - - - -STRUCT_PERSON - -Person -age: int - - - -STRUCT_TOP->STRUCT_PERSON - - - - - -STRUCT_S1 - -s1 -x: int - - - -STRUCT_TOP->STRUCT_S1 - - - - - -STRUCT_S2 - -s2 -x: int, y: int - - - -STRUCT_TOP->STRUCT_S2 - - - - - -STRUCT_MORE - -… - - - -STRUCT_TOP->STRUCT_MORE - - - - - - -STRUCT_BOT - -⊥:struct -[struct bottom] - - - -STRUCT_BOT->BOT - - - - - -STRUCT_PERSON->STRUCT_BOT - - - - - - -STRUCT_S1->STRUCT_BOT - - - - - - -STRUCT_S2->STRUCT_BOT - - - - - - -STRUCT_MORE->STRUCT_BOT - - - - - -MEM_BOT - -⊥:mem - - - -MEM_TOP->MEM_BOT - - - - - - -MEM_BOT->BOT - - - - - -TUPLE1 - - - -tuple - - -type - - - -TUPLE_TOP->TUPLE1 - - - - - -TUPLE2 - - - -tuple - - -type - - -type - - - -TUPLE_TOP->TUPLE2 - - - - - -TUPLEN - - - -tuple - - -type - - -… - - -type - - - -TUPLE_TOP->TUPLEN - - - - - -TUPLE_BOT - -⊥:tuple - - - -TUPLE_BOT->BOT - - - - - -TUPLE1->TUPLE_BOT - - - - - - -TUPLE2->TUPLE_BOT - - - - - - -TUPLEN->TUPLE_BOT - - - - - diff --git a/chapter10a/docs/load-after-store-after.json b/chapter10a/docs/load-after-store-after.json deleted file mode 100644 index d27efe5b..00000000 --- a/chapter10a/docs/load-after-store-after.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "ver": 1, - "comp": "load-after-store-after", - "step": 1, - "roots": [10], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Box", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 4, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/load-after-store-after.svg b/chapter10a/docs/load-after-store-after.svg deleted file mode 100644 index 78867d38..00000000 --- a/chapter10a/docs/load-after-store-after.svg +++ /dev/null @@ -1,97 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #4 · DATA#10[1] → #8 · MEM#10[2] → #4 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Box  #5#5 new Box -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.x  #8#8 Store p.x -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #4 DATA0123Return  #10#10 Return -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #4 DATA012 diff --git a/chapter10a/docs/load-after-store-before.json b/chapter10a/docs/load-after-store-before.json deleted file mode 100644 index ba4e2e0b..00000000 --- a/chapter10a/docs/load-after-store-before.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "ver": 1, - "comp": "load-after-store-before", - "step": 1, - "roots": [10], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Box", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10a/docs/load-after-store-before.svg b/chapter10a/docs/load-after-store-before.svg deleted file mode 100644 index 497c915d..00000000 --- a/chapter10a/docs/load-after-store-before.svg +++ /dev/null @@ -1,103 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #7 · MEM#8[2] → #5 · DATA#8[3] → #4 · DATA#9[1] → #8 · MEM#9[2] → #5 · DATA#10[1] → #8 · MEM#10[2] → #9 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Box  #5#5 new Box -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7#7 Store p.x -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.x  #8#8 Store p.x -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #4 DATA0123Load p.x  #9#9 Load p.x -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → — DATA0123Return  #10#10 Return -Type not computed - -[0] → — CTRL -[1] → #8 MEM -[2] → #9 DATA012 diff --git a/chapter10a/docs/type-control.svg b/chapter10a/docs/type-control.svg deleted file mode 100644 index d4b79c53..00000000 --- a/chapter10a/docs/type-control.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Control - - Control - diff --git a/chapter10a/docs/type-integer.svg b/chapter10a/docs/type-integer.svg deleted file mode 100644 index 97d03372..00000000 --- a/chapter10a/docs/type-integer.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Integer - - Integer - diff --git a/chapter10a/docs/type-memory.svg b/chapter10a/docs/type-memory.svg deleted file mode 100644 index 16b5121f..00000000 --- a/chapter10a/docs/type-memory.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Memory - - Memory - diff --git a/chapter10a/docs/type-pointer.svg b/chapter10a/docs/type-pointer.svg deleted file mode 100644 index 7604492e..00000000 --- a/chapter10a/docs/type-pointer.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Pointer - - Pointer - diff --git a/chapter10a/docs/type-struct.svg b/chapter10a/docs/type-struct.svg deleted file mode 100644 index 97a16562..00000000 --- a/chapter10a/docs/type-struct.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Struct - - Struct - diff --git a/chapter10a/docs/type-tuple.svg b/chapter10a/docs/type-tuple.svg deleted file mode 100644 index 691478ac..00000000 --- a/chapter10a/docs/type-tuple.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Tuple - - Tuple - diff --git a/chapter10a/pom.xml b/chapter10a/pom.xml deleted file mode 100644 index d0f1faae..00000000 --- a/chapter10a/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.seaofnodes - simple - 1.0 - - chapter10a - jar - Chapter 10a - - ${project.basedir}/../graph - ${project.basedir}/../print - - diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java deleted file mode 100644 index 621bc2d6..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.print.IRAdapter; -import com.seaofnodes.simple.node.*; - -/** This chapter's read-only IR facts and the familiar debugger entry points. */ -public final class IRPrinter extends IRAdapter { - private static final com.seaofnodes.print.IRPrinter PRINT = - new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); - @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } - @Override public int inputColumns() { return 3; } - @Override public Kind kind(Node n) { - if( n instanceof StartNode ) return Kind.START; - if( n instanceof StopNode ) return Kind.STOP; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof ReturnNode ) return Kind.RETURN; - if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof ProjNode ) return n.isCFG() ? Kind.CPROJ : Kind.PROJ; - if( n.isCFG() ) return Kind.CTRL; - if( n instanceof MultiNode ) return Kind.MULTI; - return Kind.DATA; - } - @Override public int index(Node n) { - if( n instanceof ProjNode p ) return p._idx; - return n._nid; - } - - public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index 7075d526..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Random; - -/** - * The IterOptim runs after parsing. It iterates the peepholes to a fixed point - * so that no more peepholes apply. This should be linear because peepholes rarely - * (never?) increase code size. The graph should monotonically reduce in some - * dimension, which is usually size. It might also reduce in e.g. number of - * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper - * ones. - * - * The theoretical overall worklist is mindless just grabbing the next thing and - * doing it. If the graph changes, put the neighbors on the worklist. Lather, - * Rinse, Repeat until the worklist runs dry. - * - * The main issues we have to deal with: - * - *
    - *
  • Nodes have uses; replacing some set of Nodes with another requires more graph - * reworking. Not rocket science, but it can be fiddly. Its helpful to have a - * small set of graph munging utilities, and the strong invariant that the graph - * is stable and correct between peepholes. In our case `Node.subsume` does - * most of the munging, building on our prior stable Node utilities.
  • - * - *
  • Changing a Node also changes the graph "neighborhood". The neigbors need to - * be checked to see if THEY can also peephole, and so on. After any peephole - * or graph update we put a Nodes uses and defs on the worklist.
  • - * - *
  • Our strong invariant is that for all Nodes, either they are on the worklist - * OR no peephole applies. This invariant is easy to check, although expensive. - * Basically the normal "iterate peepholes to a fixed point" is linear, and this - * check is linear at each peephole step... so quadratic overall. Its a useful - * assert, but one we can disable once the overall algorithm is stable - and - * then turn it back on again when some new set of peepholes is misbehaving. - * The code for this is turned on in `IterOptim.iterate` as `assert - * progressOnList(stop);`
  • - *
- */ -public abstract class IterPeeps { - - private static final WorkList WORK = new WorkList<>(); - - public static N add( N n ) { - return (N)WORK.push(n); - } - - public static void addAll( ArrayList ary ) { - WORK.addAll(ary); - } - - /** - * Iterate peepholes to a fixed point - */ - public static StopNode iterate(StopNode stop) { - assert progressOnList(stop); - int cnt=0; - - Node n; - 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() ) { - 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 - if( x != n || !(x instanceof ConstantNode) ) { - // All outputs of n (changing node) not x (prior existing node). - for( Node z : n._outputs ) WORK.push(z); - // Everybody gets a free "go again" in case they didn't get - // made in their final form. - WORK.push(x); - // If the result is not self, revisit all inputs (because - // there's a new user), and replace in the graph. - if( x != n ) { - for( Node z : n. _inputs ) WORK.push(z); - n.subsume(x); - } - } - // If there are distant neighbors, move to worklist - n.moveDepsToWorklist(); - 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); - } - - return stop; - } - - // Visit ALL nodes and confirm the invariant: - // Either you are on the WORK worklist OR running `iter()` makes no progress. - - // This invariant ensures that no progress is missed, i.e., when the - // worklist is empty we have indeed done all that can be done. To help - // with debugging, the {@code assert} is broken out in a place where it is easy to - // stop if a change is found. - - // Also, the normal usage of `iter()` may attempt peepholes with distance - // neighbors and these should fail, but will then try to add dependencies - // {@link #Node.addDep} which is a side effect in an assert. The {@link - // #midAssert} is used to stop this side effect. - private static boolean MID_ASSERT; - public static boolean midAssert() { return MID_ASSERT; } - private static boolean progressOnList(Node stop) { - MID_ASSERT = true; - int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; - Node changed = stop.walk( n -> { - if( WORK.on(n) ) return null; - Node m = n.peepholeOpt(); - if( m==null ) return null; - System.err.println("BREAK HERE FOR BUG"); - return m; - }); - Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop; - MID_ASSERT = false; - return changed==null; - } - - public static void reset() { - WORK.clear(); - } - - /** - * Classic WorkList, with a fast add/remove, dup removal, random pull. - * The Node's nid is used to check membership in the worklist. - */ - public static class WorkList { - - private Node[] _es; - private int _len; - private final BitSet _on; // Bit set if Node._nid is on WorkList - private final Random _R; // For randomizing pull from the WorkList - private final long _seed; - - /* Useful stat - how many nodes are processed in the post parse iterative opt */ - private long _totalWork = 0; - - WorkList() { this(123); } - WorkList(long seed) { - _es = new Node[1]; - _len=0; - _on = new BitSet(); - _seed = seed; - _R = new Random(); - _R.setSeed(_seed); - } - - /** - * Pushes a Node on the WorkList, ensuring no duplicates - * If Node is null it will not be added. - */ - public E push( E x ) { - if( x==null ) return null; - int idx = x._nid; - if( !_on.get(idx) ) { - _on.set(idx); - if( _len==_es.length ) - _es = Arrays.copyOf(_es,_len<<1); - _es[_len++] = x; - _totalWork++; - } - return x; - } - - public void addAll( ArrayList ary ) { - for( E n : ary ) - push(n); - } - - /** - * True if Node is on the WorkList - */ - boolean on( E x ) { return _on.get(x._nid); } - - /** - * Removes a random Node from the WorkList; null if WorkList is empty - */ - E pop() { - if( _len == 0 ) return null; - int idx = _R.nextInt(_len); - E x = (E)_es[idx]; - _es[idx] = _es[--_len]; // Compress array - _on.clear(x._nid); - return x; - } - - public void clear() { - _len = 0; - _on.clear(); - _R.setSeed(_seed); - _totalWork = 0; - } - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 7734f4df..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,827 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.graph.GraphObserver; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; - -import java.util.*; - -/** - * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract - * Syntax Tree structure. - *

- * This is a simple recursive descent parser. All lexical analysis is done here as well. - */ -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 - * through the entire parser and optimizer. - *

- * To make the compiler multithreaded, this field will have to move into a TLS. - */ - public static StartNode START; - - public StopNode STOP; - - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. - private final Lexer _lexer; - - /** - * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time - * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how - * we handle branching. See {@link #parseIf()}. - *

- * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds - * variable names to Nodes. The top of this stack represents current scope. - *

- * We keep a list of all ScopeNodes so that we can show them in graphs. - * @see #parseIf() - * @see #_xScopes - */ - public ScopeNode _scope; - - /** - * List of keywords disallowed as identifiers - */ - private static final HashSet KEYWORDS = new HashSet<>(){{ - add("break"); - add("continue"); - add("else"); - add("false"); - add("if"); - add("int"); - add("new"); - add("null"); - add("return"); - add("struct"); - add("true"); - add("while"); - }}; - - - /** - * We clone ScopeNodes when control flows branch; it is useful to have - * a list of all active ScopeNodes for purposes of visualization of the SoN graph - */ - public final Stack _xScopes = new Stack<>(); - - ScopeNode _continueScope; - ScopeNode _breakScope; - - // Mapping from a Struct name to a Struct. - public static Map OBJS = new HashMap<>(); - - public Parser(String source, TypeInteger arg) { - PARSER = this; - Node.reset(); - IterPeeps.reset(); - OBJS.clear(); - _lexer = new Lexer(source); - _scope = new ScopeNode(); - _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); - STOP = new StopNode(source); - } - - public Parser(String source) { - this(source, TypeInteger.BOT); - } - - @Override - public String toString() { return _lexer.toString(); } - - String src() { return new String( _lexer._input ); } - - // Debugging utility to find a Node by index - public static Node find(int nid) { return START.find(nid); } - - private Node ctrl() { return _scope.ctrl(); } - - private Node ctrl(Node n) { return _scope.ctrl(n); } - - public StopNode parse() { - _xScopes.push(_scope); - // Enter a new scope for the initial control and arguments - _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); - _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); - parseBlock(); - _scope.pop(); - _xScopes.pop(); - if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); - STOP.peephole(); - return STOP; - } - - /** - * Parses a block - * - *

-     *     '{' statements '}'
-     * 
- * Does not parse the opening or closing '{}' - * @return a {@link Node} or {@code null} - */ - private Node parseBlock() { - // Enter a new scope - _scope.push(); - while (!peek('}') && !_lexer.isEOF()) - parseStatement(); - // Exit scope - _scope.pop(); - return null; - } - - /** - * Parses a statement - * - *
-     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
-     * 
- * @return a {@link Node} or {@code null} - */ - private Node parseStatement() { - if( false ) return null; - else if (matchx("return") ) return parseReturn(); - else if (matchx("int") ) return parseDecl(TypeInteger.BOT); - else if (match ("{") ) return require(parseBlock(),"}"); - else if (matchx("if") ) return parseIf(); - else if (matchx("while") ) return parseWhile(); - else if (matchx("break") ) return parseBreak(); - else if (matchx("continue")) return parseContinue(); - else if (matchx("struct") ) return parseStruct(); - else if (matchx(";") ) return null; // Empty statement - // declarations of vars with struct type are handled in parseExpressionStatement due - // to ambiguity - else return parseExpressionStatement(); - } - - /** - * Parse a struct field. - *
-     *     int IDENTIFIER ;
-     * 
- */ - private Field parseField(String sname) { - if( matchx("int") ) // Currently only parsing "int" type fields - return require(Field.make(requireId().intern(),TypeInteger.BOT),";"); - throw errorSyntax("A field type is expected, only type 'int' is supported at present"); - } - - /** - * Parse a struct declaration, and return the following statement. - * Only allowed in top level scope. - * Structs cannot be redefined. - * - * @return The statement following the struct - */ - private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); - String typeName = requireId(); - if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); - ArrayList fields = new ArrayList<>(); - require("{"); - while (!peek('}') && !_lexer.isEOF()) { - Field field = parseField(typeName); - if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); - fields.add(field); - } - require("}"); - // Build and install the TypeStruct - TypeStruct ts = TypeStruct.make(typeName, fields); - OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names - return parseStatement(); - } - - /** - * Parses a while statement - * - *
-     *     while ( expression ) statement
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseWhile() { - - var savedContinueScope = _continueScope; - var savedBreakScope = _breakScope; - - require("("); - - // Loop region has two control inputs, the first is the entry - // point, and second is back edge that is set after loop is parsed - // (see end_loop() call below). Note that the absence of back edge is - // used as an indicator to switch off peepholes of the region and - // associated phis; see {@code inProgress()}. - - ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here - - // At loop head, we clone the current Scope (this includes all - // names in every nesting level within the Scope). - // We create phis eagerly for all the names we find, see dup(). - - // Save the current scope as the loop head - ScopeNode head = _scope.keep(); - // Clone the head Scope to create a new Scope for the body. - // Create phis eagerly as part of cloning - _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis - - // Parse predicate - var pred = require(parseExpression(), ")"); - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); - - // Clone the body Scope to create the break/exit Scope which accounts for any - // side effects in the predicate. The break/exit Scope will be the final - // scope after the loop, and its control input is the False branch of - // the loop predicate. Note that body Scope is still our current scope. - ctrl(ifF); - _xScopes.push(_breakScope = _scope.dup()); - - // No continues yet - _continueScope = null; - - // Parse the true side, which corresponds to loop body - // Our current scope is the body Scope - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - parseStatement(); // Parse loop body - - // Merge the loop bottom into other continue statements - if (_continueScope != null) { - _continueScope = jumpTo(_continueScope); - _scope.kill(); - _scope = _continueScope; - } - - // The true branch loops back, so whatever is current _scope.ctrl gets - // added to head loop as input. endLoop() updates the head scope, and - // goes through all the phis that were created earlier. For each phi, - // it sets the second input to the corresponding input from the back - // edge. If the phi is redundant, it is replaced by its sole input. - var exit = _breakScope; - head.endLoop(_scope, exit); - head.unkeep().kill(); - - _xScopes.pop(); // Cleanup - _xScopes.pop(); // Cleanup - - _continueScope = savedContinueScope; - _breakScope = savedBreakScope; - - // At exit the false control is the current control, and - // the scope is the exit scope after the exit test. - return _scope = exit; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope - // Prune nested lexical scopes that have depth > than the loop head - // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur._scopes.size() > _breakScope._scopes.size() ) - cur.pop(); - // If this is a continue then first time the target is null - // So we just use the pruned current scope as the base for the - // "continue" - if (toScope == null) - return cur; - // toScope is either the break scope, or a scope that was created here - assert toScope._scopes.size() <= _breakScope._scopes.size(); - toScope.ctrl(toScope.mergeScopes(cur)); - return toScope; - } - - private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } - - private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); } - private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } - - /** - * Parses a statement - * - *
-     *     if ( expression ) statement [else statement]
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseIf() { - require("("); - // Parse predicate - var pred = require(parseExpression(), ")").keep(); - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); - // In if true branch, the ifT proj node becomes the ctrl - // But first clone the scope and set it as current - int ndefs = _scope.nIns(); - ScopeNode fScope = _scope.dup(); // Duplicate current scope - fScope.ctrl(ifF); // The pending arm already starts at False - _xScopes.push(fScope); // For graph visualization we need all scopes - - // Parse the true side - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.upcast(ifT,pred,false); // Up-cast predicate - parseStatement(); // Parse true-side - ScopeNode tScope = _scope; - - // Parse the false side - _scope = fScope; // Restore scope, then parse else block if any - ifF.unkeep(); // fScope already owns the false control - if (matchx("else")) { - _scope.upcast(ifF,pred,true); // Up-cast predicate - parseStatement(); - fScope = _scope; - } - pred.unkeep(); - - if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) - throw error("Cannot define a new name on one arm of an if"); - - // Merge results - _scope = tScope; - _xScopes.pop(); // Discard pushed from graph display - - return ctrl(tScope.mergeScopes(fScope)); - } - - - /** - * Parses a return statement; "return" already parsed. - * The $ctrl edge is killed. - * - *
-     *     'return' expr ;
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseReturn() { - var expr = require(parseExpression(), ";"); - Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); - ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control - return ret; - } - - - - /** - * Parses an expression statement or a declaration statement where type is a struct - * - *
-     *      name;         // Error
-     * type name;         // Define name with default initial value
-     * type name = expr;  // Define name with given   initial value
-     *      name = expr;  // Reassign existing
-     *             expr   // Something else
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpressionStatement() { - int old = _lexer._position; - Type t = type(); - String name = requireId(); - Node expr; - if( match(";") ) { // Assign a default value - // No type and no expr is an error - if( t==null ) throw errorSyntax("expression"); - expr = new ConstantNode(t.makeInit()).peephole(); - } else if( match("=") ) { // Assign "= expr;" - expr = require(parseExpression(), ";"); - } else { // Neither, so just a normal expression parse - _lexer._position = old; - return require(parseExpression(),";"); - } - - // Defining a new variable vs updating an old one - if( t != null ) { - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - } else { - Node n = _scope.lookup(name); - t = _scope.lookupDeclaredType(name); - if( n==null ) - throw error("Undefined name '" + name + "'"); - _scope.update(name,expr); - } - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - return expr; - } - - // Parse a type-or-null - private Type type() { - int old = _lexer._position; - String tname = _lexer.matchId(); - if( tname==null ) return null; - if( tname.equals("int") ) return TypeInteger.BOT; - TypeStruct obj = OBJS.get(tname); - if( obj != null ) - return TypeMemPtr.make(obj,match("?")); - // Not a type; unwind the parse - _lexer._position = old; - return null; - } - - /** - * Parses a declStatement - * - *
-     *     type name = expression ';'
-     * 
- * @return an expression {@link Node} - */ - private Node parseDecl(Type t) { - var name = requireId(); - var expr = match(";") - // Assign a null value - ? new ConstantNode(t.makeInit()).peephole() - // Assign "= expr;" - : require(require("=").parseExpression(), ";"); - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - return expr; - } - - - /** - * Parse an expression of the form: - * - *
-     *     expr : compareExpr
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpression() { return parseComparison(); } - - /** - * Parse an expression of the form: - * - *
-     *     expr : additiveExpr op additiveExpr
-     * 
- * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - var lhs = parseAddition(); - while( true ) { - int idx=0; boolean negate=false; - // Test for any local nodes made, and "keep" lhs during peepholes - if( false ) ; - else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } - else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } - else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } - else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } - else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } - else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } - else break; - // Peepholes can fire, but lhs is already "hooked", kept alive - lhs.setDef(idx,parseAddition()); - lhs = lhs.peephole(); - if( negate ) // Extra negate for != - lhs = new NotNode(lhs).peephole(); - } - return lhs; - } - - /** - * Parse an additive expression - * - *
-     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
-     * 
- * @return an add expression {@link Node}, never {@code null} - */ - private Node parseAddition() { - Node lhs = parseMultiplication(); - while( true ) { - if( false ) ; - else if( match("+") ) lhs = new AddNode(lhs,null); - else if( match("-") ) lhs = new SubNode(lhs,null); - else break; - lhs.setDef(2,parseMultiplication()); - lhs = lhs.peephole(); - } - return lhs; - } - - /** - * Parse an multiplicativeExpr expression - * - *
-     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
-     * 
- * @return a multiply expression {@link Node}, never {@code null} - */ - private Node parseMultiplication() { - var lhs = parseUnary(); - while( true ) { - if( false ) ; - else if( match("*") ) lhs = new MulNode(lhs,null); - else if( match("/") ) lhs = new DivNode(lhs,null); - else break; - lhs.setDef(2,parseUnary()); - lhs = lhs.peephole(); - } - return lhs; - } - - /** - * Parse a unary minus expression. - * - *
-     *     unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
-     * 
- * @return a unary expression {@link Node}, never {@code null} - */ - private Node parseUnary() { - if (match("-")) return new MinusNode(parseUnary()).peephole(); - if (match("!")) return new NotNode(parseUnary()).peephole(); - return parsePostfix(parsePrimary()); - } - - /** - * Parse a primary expression: - * - *
-     *     primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')'
-     * 
- * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { - if( _lexer.isNumber() ) return parseIntegerLiteral(); - if( match("(") ) return require(parseExpression(), ")"); - if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); - if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); - if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); - if( matchx("new") ) { - String structName = requireId(); - TypeStruct obj = OBJS.get(structName); - if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'"); - return newStruct(obj); - } - String name = _lexer.matchId(); - if( name == null) throw errorSyntax("an identifier or expression"); - Node n = _scope.lookup(name); - if( n!=null ) return n; - throw error("Undefined name '" + name + "'"); - } - - /** - * Return a NewNode but also generate instructions to initialize it. - */ - private Node newStruct( TypeStruct obj ) { - Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); - for( Field field : obj._fields ) { - mem(new StoreNode(field._fname, mem(), n, initValue).peephole()); - } - return n.unkeep(); - } - - // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.lookup("$mem"); } - private Node mem(Node n) { return _scope.update("$mem",n); } - - /** - * Parse postfix expression. For now this is just a field - * expression, but in future could be array index too. - * - *
-     *     expr ('.' IDENTIFIER)* [ = expr ]
-     * 
- */ - private Node parsePostfix(Node expr) { - if( !match(".") ) return expr; - - if( !(expr._type instanceof TypeMemPtr ptr) ) - throw error("Expected struct reference but got " + expr._type.str()); - - String name = requireId().intern(); - int idx = ptr._obj==null ? -1 : ptr._obj.find(name); - if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - - if( match("=") ) { - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( peek('=') ) _lexer._position--; - else { - Node val = parseExpression(); - mem(new StoreNode(name, mem(), expr, val).peephole()); - return expr; // "obj.a = expr" returns the expression while updating memory - } - } - - Type declaredType = ptr._obj._fields[idx]._type; - return parsePostfix(new LoadNode(name, declaredType, mem(), expr).peephole()); - } - - /** - * Parse integer literal - * - *
-     *     integerLiteral: [1-9][0-9]* | [0]
-     * 
- */ - private ConstantNode parseIntegerLiteral() { - return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); - } - - ////////////////////////////////// - // Utilities for lexical analysis - - // Return true and skip if "syntax" is next in the stream. - private boolean match (String syntax) { return _lexer.match (syntax); } - // Match must be "exact", not be followed by more id letters - private boolean matchx(String syntax) { return _lexer.matchx(syntax); } - // Return true and do NOT skip if 'ch' is next - private boolean peek(char ch) { return _lexer.peek(ch); } - private boolean peekIsId() { return _lexer.peekIsId(); } - - // Require and return an identifier - private String requireId() { - String id = _lexer.matchId(); - if (id != null && !KEYWORDS.contains(id) ) return id; - throw error("Expected an identifier, found '"+id+"'"); - } - - - - // Require an exact match - private Parser require(String syntax) { require(null, syntax); return this; } - private N require(N n, String syntax) { - if (match(syntax)) return n; - throw errorSyntax(syntax); - } - - RuntimeException errorSyntax(String syntax) { - return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); - } - - public static RuntimeException error( String errorMessage ) { - return new RuntimeException(errorMessage); - } - - //////////////////////////////////// - // Lexer components - - private static class Lexer { - - // Input buffer; an array of text bytes read from a file or a string - private final byte[] _input; - // Tracks current position in input buffer - private int _position = 0; - - /** - * Record the source text for lexing - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - public Lexer(byte[] buf) { - _input = buf; - } - - // Very handy in the debugger, shows the unparsed program - @Override - public String toString() { - return new String(_input, _position, _input.length - _position); - } - - // True if at EOF - private boolean isEOF() { - return _position >= _input.length; - } - - // Peek next character, or report EOF - private char peek() { - return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate - : (char) _input[_position]; - } - - private char nextChar() { - char ch = peek(); - _position++; - return ch; - } - - // True if a white space - private boolean isWhiteSpace() { - return peek() <= ' '; // Includes all the use space, tab, newline, CR - } - - /** - * Return the next non-white-space character - */ - private void skipWhiteSpace() { - while (isWhiteSpace()) _position++; - } - - - // Return true, if we find "syntax" after skipping white space; also - // then advance the cursor past syntax. - // Return false otherwise, and do not advance the cursor. - boolean match(String syntax) { - skipWhiteSpace(); - int len = syntax.length(); - if (_position + len > _input.length) return false; - for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) - return false; - _position += len; - return true; - } - - // Match must be exact and not followed by more ID characters. - // Prevents identifier "ifxy" from matching an "if" statement. - boolean matchx(String syntax) { - if( !match(syntax) ) return false; - if( !isIdLetter(peek()) ) return true; - _position -= syntax.length(); - return false; - } - - private boolean peek(char ch) { - skipWhiteSpace(); - return peek()==ch; - } - - boolean peekIsId() { - skipWhiteSpace(); - return isIdStart(peek()); - } - - // Return an identifier or null - String matchId() { - return peekIsId() ? parseId() : null; - } - - // Used for errors - String getAnyNextToken() { - if (isEOF()) return ""; - if (isIdStart(peek())) return parseId(); - if (isNumber(peek())) return parseNumberString(); - if (isPunctuation(peek())) return parsePunctuation(); - return String.valueOf(peek()); - } - - boolean isNumber() {return isNumber(peek());} - boolean isNumber(char ch) {return Character.isDigit(ch);} - - private Type parseNumber() { - String snum = parseNumberString(); - if (snum.length() > 1 && snum.charAt(0) == '0') - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(snum)); - } - private String parseNumberString() { - int start = _position; - while (isNumber(nextChar())) ; - return new String(_input, start, --_position - start); - } - - // First letter of an identifier - private boolean isIdStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - // All characters of an identifier, e.g. "_x123" - private boolean isIdLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private String parseId() { - int start = _position; - while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); - } - - private boolean isPunctuation(char ch) { - return "=;[]<>(){}+-/*!".indexOf(ch) != -1; - } - - private String parsePunctuation() { - int start = _position; - return new String(_input, start, 1); - } - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10a/src/main/java/com/seaofnodes/simple/Utils.java deleted file mode 100644 index 4f2af769..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/Utils.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple; - -import java.util.ArrayList; - -public class Utils { - public static RuntimeException TODO() { return TODO("Not yet implemented"); } - public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } - - /** - * Fast, constant-time, element removal. Does not preserve order - * - * @param array ArrayList to modify - * @param i element to be removed - * @return element removed - */ - public static E del(ArrayList array, int i) { - if ( i >= 0 && i < array.size() ) { - E tmp = array.get(i); - E last = array.removeLast(); - if (i < array.size()) array.set(i, last); - return tmp; - } - return null; - } - - /** - * Search a list for an element by reference - * - * @param ary List to search in - * @param x Object to be searched - * @return >= 0 on success, -1 on failure - */ - public static int find( ArrayList ary, E x ) { - for( int i=0; i= 0 on success, -1 on failure - */ - public static int find( E[] ary, E x ) { - for( int i=0; i>>n); } - - // Fold a 64bit hash into 32 bits - public static int fold( long x ) { return (int)((x>>32) ^ x); } - - public static boolean eq(String s0, String s1) { - if( s0==s1 ) return true; - assert s0==s0.intern(); - assert s1==s1.intern(); - return false; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index e235d288..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.*; - - -public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Add"; } - - @Override protected String format() { return "(%1+%2)"; } - - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(i0.value()+i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize () { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Add of 0. We do not check for (0+x) because this will already - // canonicalize to (x+0) - if( t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Add of same to a multiply by 2 - if( lhs==rhs ) - return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); - - // Goal: a left-spine set of adds, with constants on the rhs (which then fold). - - // Move non-adds to RHS - if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) - return swap12(); - - // x+(-y) becomes x-y - if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); - - // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) - - // Do we have x + (y + z) ? - // Swap to (x + y) + z - // Rotate (add add add) to remove the add on RHS - if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); - - // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) - // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); - - // Now we only see (add add non) - - // Dead data cycle; comes about from dead infinite loops. Do nothing, - // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) - return null; - - // Do we have (x + con1) + con2? - // Replace with (x + (con1+con2) which then fold the constants - // lhs.in(2) is con1 here - // If lhs.in(2) is not a constant, we add ourselves as a dependency - // because if it later became a constant then we could make this - // transformation. - if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); - - - // Do we have ((x + (phi cons)) + con) ? - // Do we have ((x + (phi cons)) + (phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - Node phicon = phiCon(this,true); - if( phicon!=null ) return phicon; - - // Now we sort along the spine via rotates, to gather similar things together. - - // Do we rotate (x + y) + z - // into (x + z) + y ? - if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - - return null; - } - - // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - static Node phiCon(Node op, boolean rotate) { - Node lhs = op.in(1); - Node rhs = op.in(2); - // LHS is either a Phi of constants, or another op with Phi of constants - PhiNode lphi = pcon(lhs,op); - if( rotate && lphi==null && lhs.nIns() > 2 ) { - // Only valid to rotate constants if both are same associative ops - if( lhs.getClass() != op.getClass() ) return null; - lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push - } - if( lphi==null ) return null; - - // RHS is a constant or a Phi of constants - if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) - return null; - - // If both are Phis, must be same Region - if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) - return null; - - // Note that this is the exact reverse of Phi pulling a common op down - // to reduce total op-count. We don't get in an endless push-up - // push-down peephole cycle because the constants all fold first. - Node[] ns = new Node[lphi.nIns()]; - ns[0] = lphi.in(0); - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - for( int i=1; i hi._nid; - } - - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index 63452dba..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; - - -abstract public class BoolNode extends Node { - - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - abstract String op(); // String opcode name - - @Override - public String label() { return getClass().getSimpleName(); } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.open().n(in(1)).p(op()).n(in(2)).close(); - } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); - } - return TypeInteger.BOT; - } - - abstract boolean doOp(long lhs, long rhs); - - @Override - public Node idealize() { - // Compare of same - if( in(1)==in(2) ) - return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); - - // Equals pushes constant to the right; 5==X becomes X==5. - if( this instanceof EQ ) { - if( !(in(2) instanceof ConstantNode) ) { - // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode ) - return new EQ(in(2),in(1)); - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - else if( in(1)._nid > in(2)._nid ) - return new EQ(in(2),in(1)); - } - // Equals X==0 becomes a !X - if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) - return new NotNode(in(1)); - } - - // Do we have ((phi cons) cmp con) ? - // Do we have ((phi cons) cmp (phi cons)) ? - // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) - Node phicon = AddNode.phiCon(this,this instanceof EQ); - if( phicon!=null ) return phicon; - - return null; - } - - public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } - public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } - public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index c1faf210..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.IterPeeps; - - -// Upcast (join) the input to a t. Used after guard test to lift an input. -// Can also be used to make a type-assertion if ctrl is null. -public class CastNode extends Node { - private final Type _t; - public CastNode(Type t, Node ctrl, Node in) { - super(ctrl, in); - _t = t; - setType(compute()); - } - - @Override public String label() { return "("+_t.str()+")"; } - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()).n(in(1)); - } - - @Override - public Type compute() { - return in(1)._type.join(_t); - } - - @Override - public Node idealize() { - return in(1)._type.isa(_t) ? in(1) : null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index 426736ed..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - - -/** - * A Constant node represents a constant value. At present, the only constants - * that we allow are integer literals; therefore Constants contain an integer - * value. As we add other types of constants, we will refactor how we represent - * Constants. - *

- * Constants have no semantic inputs. However, we set Start as an input to - * Constants to enable a forward graph walk. This edge carries no semantic - * meaning, and it is present solely to allow visitation. - *

- * The Constant's value is the value stored in it. - */ -public class ConstantNode extends Node { - final Type _con; - public ConstantNode( Type type ) { - super(Parser.START); - _con = type; - } - - @Override protected String repeatName() { - return _con==null || _con.toString().length()<=32 ? null : uniqueName(); - } - - @Override - public String label() { return "Con"; } - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(_con); - } - - @Override public boolean isMultiTail() { return true; } - - @Override - public Type compute() { return _con; } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { - ConstantNode con = (ConstantNode)n; // Contract - return _con==con._con; - } - - @Override - int hash() { return _con.hashCode(); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index d7244ace..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Div"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return i1.value() == 0 - ? TypeInteger.ZERO - : TypeInteger.constant(i0.value()/i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // Div of 1. - if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 ) - return in(1); - return null; - } - - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index 45f695a5..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeTuple; - -public class IfNode extends MultiNode { - - public IfNode(Node ctrl, Node pred) { - super(ctrl, pred); - IterPeeps.add(this); // Because idoms are complex, just add it - } - - @Override - public String label() { return "If"; } - - @Override protected String format() { return "if( %1 )"; } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - public Node ctrl() { return in(0); } - public Node pred() { return in(1); } - - @Override - public Type compute() { - // If the If node is not reachable then neither is any following Proj - if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) - return TypeTuple.IF_NEITHER; - Node pred = pred(); - Type t = pred._type; - // High types mean NEITHER side is reachable. - // Wait until the type falls to decide which way to go. - if( t == Type.TOP || t == TypeInteger.TOP ) - return TypeTuple.IF_NEITHER; - // If constant is 0 then false branch is reachable - // Else true branch is reachable - if (t instanceof TypeInteger ti && ti.isConstant()) - return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; - - return TypeTuple.IF_BOTH; - } - - @Override - public Node idealize() { - // Hunt up the immediate dominator tree. If we find an identical if - // test on either the true or false branch, that side wins. - if( !pred()._type.isHighOrConst() ) - for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { - setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); - return this; - } - return null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index 31c7699c..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; - - -/** - * Load represents extracting a value from inside a memory object, - * in chapter 10 this means Struct fields. - */ -public class LoadNode extends MemOpNode { - - Type _declaredType; - /** - * Load a value from a ptr.field. - * - * @param name The field we are loading - * @param memSlice The whole-memory node - this is updated after a Store - * @param memPtr The ptr to the struct from where we load a field - */ - public LoadNode(String name, Type glb, Node memSlice, Node memPtr) { - super(name, memSlice, memPtr, null); - _declaredType = glb; - } - - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); - } - - // A Store directly clobbers this memory. A Phi might lead to a clobber - // through a cycle; conservatively stop rather than searching beyond it. - private boolean clobbered(Node dep) { - Node mem = mem(); - mem.addDepForwards(dep); - for( Node use : mem._outputs ) { - if( use==null ) continue; - use.addDepForwards(dep); - if( use instanceof StoreNode || use instanceof PhiNode ) return true; - } - return false; - } - - @Override - public String label() { return "Load"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { return p.p(".").p(_name); } - - @Override - public Type compute() { - return _declaredType; - } - - @Override - public Node idealize() { - - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - ptr() == st.ptr() && _name.equals(st._name) ) { // Must check same object - return st.val(); - } - - // Push a Load up through a Phi, as long as it collapses on at least - // one arm. If at a Loop, the backedge MUST collapse - else we risk - // spinning the same transform around the loop indefinitely. - // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): - // if( pred ) ptr.x = e0; val = pred ? e0 - // else ptr.x = e1; : e1; - // val = ptr.x; ptr.x = val; - if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { - // Profit on RHS/Loop backedge - if( profit(memphi,2) || - // Else must not be a loop to count profit on LHS. - (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - Node ld1 = new LoadNode(_name,_declaredType,memphi.in(1),ptr()).peephole(); - Node ld2 = new LoadNode(_name,_declaredType,memphi.in(2),ptr()).peephole(); - return new PhiNode(_name,_type,memphi.region(),ld1,ld2); - } - } - - return null; - } - - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _name.equals(st1._name); - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java deleted file mode 100644 index a0ba973e..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; - -import java.util.BitSet; - -public class LoopNode extends RegionNode { - - public LoopNode( Node entry ) { super(null,entry,null); } - - Node entry() { return in(1); } - Node back () { return in(2); } - - @Override - public String label() { return "Loop"; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - return entry()._type; - } - - @Override - public Node idealize() { - return inProgress() ? null : super.idealize(); - } - - // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) - @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } - @Override Node idom() { return entry(); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index 138f8fa5..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - -/** - * Convenience common base for Load and Store. - */ -public abstract class MemOpNode extends Node { - - public final String _name; - - public MemOpNode(String name, Node memSlice, Node memPtr, Node value) { - super(null, memSlice, memPtr, value); - _name = name; - } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - - // Extra conditions for factoring matching memory operations through a Phi. - // The caller has already checked the opcode, input shape and control. - boolean canDrop(MemOpNode other, Node dep) { - ptr().addDep(dep); - if( !_name.equals(other._name) || err()!=null ) return false; - return !(ptr()._type instanceof TypeMemPtr p && - other.ptr()._type instanceof TypeMemPtr q && p._obj!=q._obj); - } - - @Override - boolean eq(Node n) { - MemOpNode mem = (MemOpNode)n; // Invariant - return _name.equals(mem._name); - } - - @Override - int hash() { return _name.hashCode(); } - - @Override - String err() { - Type ptr = ptr()._type; - return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) ) - ? "Might be null accessing '" + _name + "'" - : null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java deleted file mode 100644 index 5b65b3fb..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class MinusNode extends Node { - public MinusNode(Node in) { super(null, in); } - - @Override public String label() { return "Minus"; } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeInteger i0) - return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; - return in(1)._type==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusNode minus ) - return minus.in(1); - - return null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java deleted file mode 100644 index 5592c52d..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class MulNode extends Node { - public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Mul"; } - - @Override protected String format() { return "(%1*%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(i0.value()*i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x * (phi cons)) * con) ? - // Do we have ((x * (phi cons)) * (phi cons)) ? - // Push constant up through the phi: x * (phi con0*con0 con1*con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java deleted file mode 100644 index 2743bd19..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.node; - -public abstract class MultiNode extends Node { - - public MultiNode(Node... inputs) { super(inputs); } - - // Find a projection by index - ProjNode proj( int idx ) { - for( Node out : _outputs ) - if( out instanceof ProjNode prj && prj._idx==idx ) - return prj; - return null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java deleted file mode 100644 index 857920bc..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - - -public class NewNode extends Node { - - TypeMemPtr _ptr; - - public NewNode(TypeMemPtr ptr, Node ctrl) { - super(ctrl); - this._ptr = ptr; - } - - @Override - public String label() { return "new"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p("new ").p(_ptr._obj.str()); - } - - @Override - public Type compute() { return _ptr; } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { return this == n; } - - @Override - int hash() { return _ptr.hashCode(); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index d0752af1..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,598 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.BaseNode; - -import com.seaofnodes.simple.Parser; - -import com.seaofnodes.simple.IRPrinter; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; - -import java.util.*; -import java.util.function.Function; - -/** - * All Nodes in the Sea of Nodes IR inherit from the Node class. - * The Node class provides common functionality used by all subtypes. - * Subtypes of Node specialize by overriding methods. - */ -public abstract class Node extends BaseNode implements Cloneable { - - /** - * Inputs to the node. These are use-def references to Nodes. - *

- * Generally fixed length, ordered, nulls allowed, no unused trailing space. - * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a {@link #isCFG} node. - */ - public ArrayList _inputs; - - /** - * Outputs reference Nodes that are not null and have this Node as an - * input. These nodes are users of this node, thus these are def-use - * references to Nodes. - *

- * Outputs directly match inputs, making a directed graph that can be - * walked in either direction. These outputs are typically used for - * efficient optimizations but otherwise have no semantics meaning. - */ - public ArrayList _outputs; - - /** - * Current computed type for this Node. This value changes as the graph - * changes and more knowledge is gained about the program. - */ - public Type _type; - - /** - * A private Global Static mutable counter, for unique node id generation. - * To make the compiler multithreaded, this field will have to move into a TLS. - * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. - * */ - private static int UNIQUE_ID = 1; - public static int UID() { return UNIQUE_ID; } - - protected Node(Node... inputs) { - super(UNIQUE_ID++); // allocate unique dense ID - _inputs = new ArrayList<>(); - Collections.addAll(_inputs,inputs); - _outputs = new ArrayList<>(); - for( Node n : _inputs ) - if( n != null ) - n.addUse( this ); - } - - // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); - - public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - - public boolean isMultiHead() { return false; } - public boolean isMultiTail() { return false; } - - // ------------------------------------------------------------------------ - // Graph Node & Edge manipulation - - @Override public String typeName() { return _type==null ? null : _type.toString(); } - - /** - * Gets the ith input node - * @param i Offset of the input node - * @return Input node or null - */ - public Node in(int i) { return _inputs.get(i); } - public Node out(int i) { return _outputs.get(i); } - - public int nIns() { return _inputs.size(); } - - public int nOuts() { return _outputs.size(); } - - public boolean isUnused() { return nOuts() == 0; } - - public boolean isMem() { return false; } - - /** - * Change a def into a Node. Keeps the edges correct, by removing - * the corresponding use->def edge. This may make the original - * def go dead. This function is co-recursive with {@link #kill}. - *

- - * This method is the normal path for altering a Node, because it does the - * proper default edge maintenance. It also immediately kills - * Nodes that lose their last use; at times care must be taken to avoid - * killing Nodes that are being used without having an output Node. This - * definitely happens in the middle of recursive {@link #peephole} calls. - * - * @param idx which def to set - * @param new_def the new definition - * @return new_def for flow coding - */ - public Node setDef(int idx, Node new_def ) { - unlock(); - Node old_def = in(idx); - if( old_def == new_def ) return new_def; // No change - // If new def is not null, add the corresponding def->use edge - // This needs to happen before removing the old node's def->use edge as - // the new_def might get killed if the old node kills it recursively. - if( new_def != null ) - new_def.addUse(this); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); - moveDepsToWorklist(); - // Return self for easy flow-coding - return new_def; - } - - // Remove the numbered input, compressing the inputs in-place. This - // shuffles the order deterministically - which is suitable for Region and - // Phi, but not for every Node. If the def goes dead, it is recursively - // killed, which may include 'this' Node. - Node delDef(int idx) { - unlock(); - Node old_def = in(idx); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - old_def.moveDepsToWorklist(); - Utils.del(_inputs, idx); - return this; - } - - /** - * Add a new def to an existing Node. Keep the edges correct by - * adding the corresponding def->use edge. - * - * @param new_def the new definition, appended to the end of existing definitions - * @return new_def for flow coding - */ - Node addDef(Node new_def) { - unlock(); - // Add use->def edge - _inputs.add(new_def); - // If new def is not null, add the corresponding def->use edge - if( new_def != null ) - new_def.addUse(this); - return new_def; - } - - // Breaks the edge invariants, used temporarily - protected void addUse(N n) { _outputs.add(n); } - - // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. - // Return true if the output list is empty afterward. - // Error is 'use' does not exist; ok for 'use' to be null. - protected boolean delUse( Node use ) { - Utils.del(_outputs, Utils.find(_outputs, use)); - moveDepsToWorklist(); // User-count and anti-dependence queries can now change. - return _outputs.isEmpty(); - } - - // Shortcut for "popping" n nodes. A "pop" is basically a - // setDef(last,null) followed by lowering the nIns() count. - void popN(int n) { - unlock(); - for( int i=0; iuses, by setting all of its defs - * to null. This may recursively kill more Nodes, and is basically dead - * code elimination. - */ - public void kill( ) { - unlock(); - assert isUnused(); // Has no uses, so it is dead - _type=null; // Flag as dead - while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes - Node old_def = _inputs.removeLast(); - if( old_def != null ) { - IterPeeps.add(old_def);// Revisit neighbor because removed use - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - } - } - assert isDead(); // Really dead now - } - - // Mostly used for asserts and printing. - public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - - // Shortcuts to stop DCE mid-parse - // Add bogus null use to keep node alive - public N keep() { addUse(null); return (N)this; } - // Remove bogus null. - public N unkeep() { delUse(null); return (N)this; } - // Test "keep" status - public boolean iskeep() { return Utils.find(_outputs,null) != -1; } - - // Replace self with nnn in the graph, making 'this' go dead - public void subsume( Node nnn ) { - assert nnn!=this; - while( nOuts() > 0 ) { - Node n = _outputs.removeLast(); - n.unlock(); - int idx = Utils.find(n._inputs, this); - n._inputs.set(idx,nnn); - n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. - nnn.addUse(n); - } - kill(); - } - - // ------------------------------------------------------------------------ - // Graph-based optimizations - - /** - * Try to peephole at this node and return a better replacement Node. - * Always returns some not-null Node (often this). - */ - public final Node peephole() { - var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; - if( obs != null ) obs.before(this); - Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n.peephole()); - if( obs != null ) obs.after(this, n == null ? null : rez, false); - return rez; - } - - /** - * Try to peephole at this node and return a better replacement Node if - * possible. We compute a {@link Type} and then check and replace: - *

    - *
  • if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}
  • - *
  • in a future chapter we will look for a - * Common Subexpression - * to eliminate.
  • - *
  • we ask the Node for a better replacement. The "better replacement" - * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes - * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing - * math constants, remove algebraic identities and generally simplify the - * code.
  • - * - * Unlike peephole above, this explicitly returns null for no-change, or not-null - * for a better replacement (which can be this). - *
- */ - public final Node peepholeOpt( ) { - ITER_CNT++; - // Compute initial or improved Type - Type old = setType(compute()); - - // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && _type.isHighOrConst() ) - return new ConstantNode(_type).peepholeOpt(); - - // Global Value Numbering - if( _hash==0 ) { - Node n = GVN.get(this); // Will set _hash as a side effect - if( n==null ) - GVN.put(this,this); // Put in table now - else { - // Because of random worklist ordering, the two equal nodes - // might have different types. Because of monotonicity, both - // types are valid. To preserve monotonicity, the resulting - // shared Node has to have the best of both types. - n.setType(n._type.join(_type)); - _hash = 0; // Clear, since it never went in the table - return deadCodeElim(n);// Return previous; does Common Subexpression Elimination - } - } - - // Ask each node for a better replacement - Node n = idealize(); - if( n != null ) // Something changed - return n; // Report progress - - if( old==_type ) ITER_NOP_CNT++; - return old==_type ? null : this; // Report progress - } - public static int ITER_CNT, ITER_NOP_CNT; - - // m is the new Node, self is the old. - // Return 'm', which may have zero uses but is alive nonetheless. - // If self has zero uses (and is not 'm'), {@link #kill} self. - private Node deadCodeElim(Node m) { - // If self is going dead and not being returned here (Nodes returned - // from peephole commonly have no uses (yet)), then kill self. - if( m != this && isUnused() && !isDead() ) { - // Killing self - and since self recursively kills self's inputs we - // might end up killing 'm', which we are returning as a live Node. - // So we add a bogus extra null output edge to stop kill(). - m.keep(); // Keep m alive - kill(); // Kill self because replacing with 'm' - m.unkeep(); // Okay to peephole m - } - return m; - } - - /** - * This function needs to be - * Monotonic - * as it is part of a Monotone Analysis Framework. - * See for example this set of slides. - *

- * For Chapter 2, all our Types are really integer constants, and so all - * the needed properties are trivially true, and we can ignore the high - * theory. Much later on, this will become important and allow us to do - * many fancy complex optimizations trivially... because theory. - *

- * {@link #compute()} needs to be stand-alone, and cannot recursively call compute - * on its inputs, because programs are cyclic (have loops!) and this will just - * infinitely recurse until stack overflow. Instead, compute typically - * computes a new type from the {@link #_type} field of its inputs. - */ - public abstract Type compute(); - - // Set the type. Assert monotonic progress. - // If changing, add users to worklist. - public Type setType(Type type) { - Type old = _type; - assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger - if( old == type ) return old; - _type = type; // Set _type late for easier assert debugging - IterPeeps.addAll(_outputs); - moveDepsToWorklist(); - return old; - } - - /** - * This function rewrites the current Node into a more "idealized" form. - * This is the bulk of our peephole rewrite rules, and we use this to - * e.g. turn arbitrary collections of adds and multiplies with mixed - * constants into a normal form that's easy for hardware to implement. - * Example: An array addressing expression: - *

   ary[idx+1]
- * might turn into Sea-of-Nodes IR: - *
   (ary+12)+((idx+1) * 4)
- * This expression can then be idealized into: - *
   ary + ((idx*4) + (12 + (1*4)))
- * And more folding: - *
   ary + ((idx<<2) + 16)
- * And during code-gen: - *
   MOV4 Rary,Ridx,16 // or some such hardware-specific notation 
- *

- * {@link #idealize} has a very specific calling convention: - *

    - *
  • If NO change is made, return {@code null} - *
  • If ANY change is made, return not-null; this can be {@code this} - *
  • The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. - *
  • Any NEW nodes that are not directly returned DO call {@link #peephole}. - *
- *

- * Examples: - * - * - * - * - * - *
before after return comment
{@code (x+5) }{@code (x+5) }{@code null }No change
{@code (5+x) }{@code (x+5) }{@code this }Swapped arguments
{@code ((x+1)+2)}{@code (x+(1+2))}{@code (x+_) }Returns 2 new Nodes
- * - * The last entry deserves more discussion. The new Node {@code (1+2)} - * created in {@link #idealize} calls {@link #peephole} (which then folds - * into a constant). The other new Node {@code (x+3)} does not call - * peephole, because it is returned and peephole itself will recursively - * call peephole. - *

- * Since idealize calls peephole and peephole calls idealize, you must be - * careful that all idealizations are monotonic: all transforms - * remove some feature, so that the set of available transforms always - * shrinks. If you don't, you risk an infinite peephole loop! - * - * @return Either a new or changed node, or null for no changes. - */ - public abstract Node idealize(); - - // Some of the peephole rules get complex, and search further afield than - // just the nearest neighbor. These peepholes can fail the pattern match - // on a node some distance away, and if that node ever changes we should - // 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 - * or output of this node, that is, it is at least one step away. The node - * being added must benefit from this node being peepholed. - */ - Node addDep(Node dep) { return addDep(dep,false); } - Node addDepForwards(Node dep) { return addDep(dep,true); } - private Node addDep(Node dep, boolean forwards) { - // 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( !forwards && Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; - _deps.add(dep); - return this; - } - - // Move the dependents onto a worklist, and clear for future dependents. - public void moveDepsToWorklist( ) { - if( _deps==null ) return; - IterPeeps.addAll(_deps); - _deps.clear(); - } - - // Global Value Numbering. Hash over opcode and inputs; hits in this table - // are structurally equal. - public static final HashMap GVN = new HashMap<>(); - - // Two nodes are equal if they have the same inputs and the same "opcode" - // which means the same Java class, plus same internal parts. - @Override public final boolean equals( Object o ) { - if( o==this ) return true; - if( o.getClass() != getClass() ) return false; - Node n = (Node)o; - int len = _inputs.size(); - if( len != n._inputs.size() ) return false; - for( int i=0; i>13) ^ n._nid; - if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing - return (_hash=hash); - } - // Subclasses add extra hash info (such as ConstantNodes constant) - int hash() { return 0; } - - // ------------------------------------------------------------------------ - // Peephole utilities - - // Swap inputs without letting either input go dead during the swap. - Node swap12() { - unlock(); // Hash is order dependent - Node tmp = in(1); - _inputs.set(1,in(2)); - _inputs.set(2,tmp); - return this; - } - - /** - * Does this node contain all constants? Ignores in(0), as is usually - * control. In an input is not a constant, we add dep as a dependency to - * it because dep can make progress if the input becomes a constant later. - * It is sufficient for one of the non-const inputs to have the dependency, - * so we don't bother checking the rest. - */ - boolean allCons(Node dep) { - for( int i=1; i - * See {@link ...} - */ - public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit - - // Find the lowest common ancestor in the current dominator tree. - Node domLCA(Node rhs) { - if( rhs==null ) return this; - Node lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = lhs.idom(); - if( comp <= 0 ) rhs = rhs.idom(); - } - return lhs; - } - - int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - return _idepth = (char)depth; - } - - // Return the immediate dominator of this Node. - Node idom() { return in(0); } - - // Make a shallow copy (same class) of this Node, with given inputs and - // empty outputs and a new Node ID. The original inputs are ignored. - // Does not need to be implemented in isCFG() nodes. - Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } - - // Exact-class copy preserving operation attributes, with fresh identity - // and no edges, dependencies, or GVN membership. - public final Node copyEmpty() { - Node n; - try { n = (Node)clone(); } - catch( CloneNotSupportedException e ) { throw new AssertionError(e); } - n._nid = UNIQUE_ID++; - n._inputs = new ArrayList<>(); - n._outputs = new ArrayList<>(); - n._deps = null; - n._hash = 0; - return n; - } - - // Report any post-optimize errors - String err() { return null; } - - /** - * Used to allow repeating tests in the same JVM. This just resets the - * Node unique id generator, and is done as part of making a new Parser. - */ - public static void reset() { - UNIQUE_ID = 1; - GVN.clear(); - ITER_CNT = ITER_NOP_CNT = 0; - } - - // Utility to walk the entire graph applying a function; return the first - // not-null result. - private static final BitSet WVISIT = new BitSet(); - final public E walk( Function pred ) { - assert WVISIT.isEmpty(); - E rez = _walk(pred); - WVISIT.clear(); - return rez; - } - - private E _walk( Function pred ) { - if( WVISIT.get(_nid) ) return null; // Been there, done that - WVISIT.set(_nid); - E x = pred.apply(this); - if( x != null ) return x; - for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; - for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; - return null; - } - - /** - * Debugging utility to find a Node by index - */ - public Node find(int nid) { - return walk( n -> n._nid==nid ? n : null ); - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index 5e556390..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.*; - -public class NotNode extends Node { - public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } - - @Override protected String format() { return "(!%1)"; } - - @Override - public Type compute() { - Type t0 = in(1)._type; - switch( t0 ) { - case TypeInteger i0: - return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; - case TypeMemPtr p0: - // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot - // If input in null then true - // If input is not null ptr then false - if( p0 == TypeMemPtr.TOP ) return TypeInteger.TOP; - if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.constant(1); - if( !p0._nil ) return TypeInteger.constant(0); - return TypeInteger.BOT; - case Type t: - if( t0.getClass() != Type.class ) - // Only doing NOT on ints and ptrs - throw Utils.TODO(); - return t0==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; - } - } - - @Override - public Node idealize() { return null; } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index 24a72aa4..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.Utils; - - -public class PhiNode extends Node { - - final String _label; - - // The Phi type we compute must stay within the domain of the Phi. - // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - final Type _declaredType; - - public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } - - @Override public String label() { return "Phi_"+_label; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - p.p("Z"); - p.p("Phi("); - for( Node in : _inputs ) p.n(in).p(","); - return p.unchar(',').close(); - } - - Node region() { return in(0); } - @Override public boolean isMultiTail() { return true; } - @Override - public boolean isMem() { return _declaredType instanceof TypeMem; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? Type.TOP : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() ) return _declaredType; - // Set type to local top of the starting type - Type t = _declaredType.glb().dual(); - for (int i = 1; i < nIns(); i++) - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - if( r.in(i).addDep(this)._type != Type.XCONTROL ) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( !(region() instanceof RegionNode r ) ) - return in(1); // Input has collapse to e.g. starting control. - if( r.inProgress() || r.nIns()<=1 ) - return null; // Input is in-progress - - // If we have only a single unique input, become it. - Node live = singleUniqueInput(); - if (live != null) - return live; - - // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). - Node down; - if( same_op() && (down=drop_same_op())!=null ) return down; - - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. - if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); - } - // If merging a null-checked null and the checked value, just use the value. - // if( val ) ..; phi(Region,False=0/null,True=val); - // then replace with plain val. - if( nIns()==3 ) { - int nullx = -1; - if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; - if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; - if( nullx != -1 ) { - Node val = in(3-nullx); - if( region().idom() instanceof IfNode iff && iff.pred().addDep(this)==val ) { - // Must walk the idom on the null side to make sure we hit False. - Node idom = region().in(nullx); - while( idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof ProjNode proj && proj._idx==1 ) - return val; - } - } - } - - return null; - } - - private boolean same_op() { - Node op = in(1); - if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || - op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode ) return false; - Node busy = null; - for( int i=1; i1 ) { - for( Node use : n._outputs ) - if( use!=null && use!=this ) use.addDepForwards(this); - if( n instanceof StoreNode || busy!=null ) return false; - busy=n; - } - for( int j=1; j _print1(ExprPrinter p) { return p.p(_label); } - - @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - - public MultiNode ctrl() { return (MultiNode)in(0); } - - @Override - public Type compute() { - Type t = in(0)._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if ( ctrl() instanceof IfNode iff ) { - if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) - return iff.in(0); // We become our input control - - // Flip a negating if-test, to remove the not - if( iff.pred().addDep(this) instanceof NotNode not ) - return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - } - return null; - } - - @Override - boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } - - @Override - int hash() { return _idx; } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 8b52494a..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; - -import java.util.ArrayList; -import java.util.Arrays; - -public class RegionNode extends Node { - - public RegionNode(Node... nodes) { super(nodes); } - - @Override - public String label() { return "Region"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()).p(_nid); - } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( inProgress() ) return null; - // Delete dead paths into a Region - int path = findDeadInput(); - if( path != 0 && - // Do not delete the entry path of a loop (ok to remove the back - // edge and make the loop a single-entry Region which folds away - // the Loop). Folding the entry path confused the loop structure, - // moving the backedge to the entry point. - !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { - // Cannot use the obvious output iterator here, because a Phi - // deleting an input might recursively delete *itself*. This - // shuffles the output array, and we might miss iterating an - // unrelated Phi. So on rare occasions we repeat the loop to get - // all the Phis. - int nouts = 0; - while( nouts != nOuts() ) { - nouts = nOuts(); - for( int i=0; i1 && in(nIns()-1) == null; - } - - // Never equal if inProgress - @Override boolean eq( Node n ) { - return !inProgress(); - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index ef4ddb3a..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - - -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ -public class ReturnNode extends Node { - - public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - super(ctrl, scope.lookup("$mem"), data); - } - - public Node ctrl() { return in(0); } - public Node mem () { return in(1); } - public Node expr() { return in(2); } - - @Override - public String label() { return "Return"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("return "); - p.n(expr()); - return p.p(";"); - } - - @Override public boolean isCFG() { return true; } - - @Override - public Type compute() { - // Return exposes the complete memory state. - return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); - } - - @Override - public Node idealize() { - if( ctrl()._type==Type.XCONTROL ) - return ctrl(); - return null; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index d567feb5..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.TypeMemPtr; - -import java.util.*; - -/** - * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of scopes. - */ -public class ScopeNode extends Node { - - /** - * The control is a name that binds to the currently active control - * node in the graph - */ - public static final String CTRL = "$ctrl"; - public static final String ARG0 = "arg"; - - /** - * Names for every input edge - */ - public final Stack> _scopes; - - /** - * Tracks declared types for every name - */ - public Stack> _types; - - - // A new ScopeNode - public ScopeNode() { - _scopes = new Stack<>(); - _types = new Stack<>(); - _type = Type.BOTTOM; - } - - - @Override public String label() { return "Scope"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("Scope[ "); - String[] names = reverseNames(); - for( int j=0; j syms : _scopes ) - for( String name : syms.keySet() ) - names[syms.get(name)] = name; - return names; - } - - @Override public Type compute() { return Type.BOTTOM; } - - @Override public Node idealize() { return null; } - - public void push() { _scopes.push(new HashMap<>()); _types.push(new HashMap<>()); } - public void pop() { popN(_scopes.pop().size()); _types.pop(); } - - /** - * Create a new name in the current scope - */ - public Node define( String name, Type declaredType, Node n ) { - HashMap syms = _scopes.lastElement(); - _types.lastElement().put(name,declaredType); - if( syms.put(name,nIns()) != null ) - return null; // Double define - return addDef(n); - } - /** - * Lookup a name in all scopes starting from most deeply nested. - * - * @param name Name to be looked up - * @see #update(String, Node, int) - */ - public Node lookup( String name ) { return update(name,null,_scopes.size()-1); } - /** - * If the name is present in any scope, then redefine else null - * - * @param name Name being redefined - * @param n The node to bind to the name - */ - public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); } - /** - * Both recursive lookup and update. - *

- * A shared implementation allows us to create lazy phis both during - * lookups and updates; the lazy phi creation is part of chapter 8. - * - * @param name Name whose binding is being updated or looked up - * @param n If null, do a lookup, else update binding - * @param nestingLevel The starting nesting level - * @return node being looked up, or the one that was updated - */ - private Node update( String name, Node n, int nestingLevel ) { - if( nestingLevel<0 ) return null; // Missed in all scopes, not found - var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level - var idx = syms.get(name); - if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope - Node old = in(idx); - if( old instanceof ScopeNode loop ) { - // Lazy Phi! - old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? loop.in(idx) - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); - setDef(idx,old); - } - return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer - } - - // Return declared type - public Type lookupDeclaredType( String name ) { - for( int i=_types.size(); i>0; i-- ) { - Type t = _types.get(i-1).get(name); - if( t != null ) return t; - } - return null; - } - - public Node ctrl() { return in(0); } - - /** - * The ctrl of a ScopeNode is always bound to the currently active - * control node in the graph, via a special name '$ctrl' that is not - * a valid identifier in the language grammar and hence cannot be - * referenced in Simple code. - * - * @param n The node to be bound to '$ctrl' - * - * @return Node that was bound - */ - public Node ctrl(Node n) { return setDef(0,n); } - - /** - * Duplicate a ScopeNode; including all levels, up to Nodes. So this is - * neither shallow (would dup the Scope but not the internal HashMap - * tables), nor deep (would dup the Scope, the HashMap tables, but then - * also the program Nodes). - *

- * If the {@code loop} flag is set, the edges are filled in as the original - * Scope, as a indication of Lazy Phis at loop heads. The goal here is to - * not make Phis at loop heads for variables which are never touched in the - * loop body. - *

- * The new Scope is a full-fledged Node with proper use<->def edges. - */ - public ScopeNode dup() { return dup(false); } - public ScopeNode dup(boolean loop) { - ScopeNode dup = new ScopeNode(); - // Our goals are: - // 1) duplicate the name bindings of the ScopeNode across all stack levels - // 2) Make the new ScopeNode a user of all the nodes bound - // 3) Ensure that the order of defs is the same to allow easy merging - for( HashMap syms : _scopes ) - dup._scopes.push(new HashMap<>(syms)); - for( HashMap ts : _types ) - dup._types.push(ts); // Types don't change, just keep stacks aligned - - dup.addDef(ctrl()); // Control input is just copied - for( int i=1; i _print1(ExprPrinter p) { - return p.p(label()); - } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - @Override - public TypeTuple compute() { return _args; } - - @Override - public Node idealize() { return null; } - - // No immediate dominator, and idepth==0 - @Override int idepth() { return 0; } - @Override Node idom() { return null; } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index 4b4e9615..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.IterPeeps; - - -public class StopNode extends Node { - - public final String _src; - - public StopNode(String src) { - super(); - _src = src; - } - - @Override - public String label() { - return "Stop"; - } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( ret()!=null ) return p.n(ret()); - p.p("Stop[ "); - for( Node ret : _inputs ) - p.n(ret).p(" "); - return p.p("]"); - } - - @Override public boolean isCFG() { return true; } - - // If a single Return, return it. - // Otherwise, null because ambiguous. - public ReturnNode ret() { - return nIns()==1 ? (ReturnNode)in(0) : null; - } - - @Override - public Type compute() { - return Type.BOTTOM; - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i { - String msg = n.err(); - if( msg != null && obs != null ) obs.error(n, msg); - return msg; - }); - if( err != null ) throw new RuntimeException(err); - if( obs != null ) obs.phase("TypeCheck"); - return this; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java deleted file mode 100644 index 71eb0842..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - - -/** - * Store represents setting a value to a memory based object, in chapter 10 - * this means a field inside a struct. - */ -public class StoreNode extends MemOpNode { - - /** - * @param name The struct field we are assigning to - * @param memSlice The whole-memory node - this is updated after a Store - * @param memPtr The ptr to the struct where we will store a value - * @param value Value to be stored - */ - public StoreNode(String name, Node memSlice, Node memPtr, Node value) { - super(name, memSlice, memPtr, value); - } - - @Override - public String label() { return "Store"; } - @Override - public boolean isMem() { return true; } - public Node val() { return in(3); } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - return p.p(".").p(_name).p("=").p( val()).p(";"); - } - - @Override - public Type compute() { return TypeMem.BOT; } - - @Override - public Node idealize() { - - // Simple store-after-store on same address. Should pick up the - // required init-store being stomped by a first user store. - if( mem() instanceof StoreNode st && - ptr()==st.ptr() && _name.equals(st._name) && // Must check same object - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - st.checkNoUseBeyond(this) ) { - setDef(1,st.mem()); - return this; - } - - return null; - } - - // Check that `this` has no uses beyond `that` - private boolean checkNoUseBeyond(Node that) { - if( nOuts()==1 ) return true; - // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that - // when the other uses go away we can retry. - for( Node use : _outputs ) - if( use != that ) - use.addDep(that); - return false; - } - -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java deleted file mode 100644 index 6fa87a07..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class SubNode extends Node { - public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Sub"; } - - @Override protected String format() { return "(%1-%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - // Sub of same is 0 - if( in(1)==in(2) ) - return TypeInteger.ZERO; - - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(i0.value()-i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Keep invalid operands for type checking. - if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { - if( y.isConstant() && y.value()==0 ) return lhs; - if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); - } - - // x - (-y) is x+y - if( rhs instanceof MinusNode minus ) - return new AddNode(lhs,minus.in(1)); - - // (-x) - y is -(x+y) - if( lhs instanceof MinusNode minus ) - return new MinusNode(new AddNode(minus.in(1),rhs).peephole()); - - return null; - } - - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java deleted file mode 100644 index 92d970d8..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphAdapter; -import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMem; - -/** Chapter 10's view of the IR; browser, transport and layout are shared. */ -public class SimpleGraphAdapter extends GraphAdapter { - - @Override protected String[] edgeNames(Node n) { - return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; - } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : -1; } - - @Override protected Kind kind(Node n) { - if( n instanceof StopNode ) return Kind.STOP; - 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; - } - - @Override protected 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/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java deleted file mode 100644 index 1980659e..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ /dev/null @@ -1,45 +0,0 @@ -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); - _phase = "TypeCheck"; - _parser.STOP.typeCheck(); - } - @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 Node scope() { return _parser._scope; } - @Override protected void roots(ArrayList roots) { - roots.add(Parser.START); - roots.add(_parser.STOP); - roots.addAll(_parser._xScopes); - roots.add(_ret); - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index cdc58215..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a field in a struct. This is not a Type in the type system. - */ -public class Field extends Type { - - // The pair {fieldName,type} uniquely identifies a field. - - // Field name - public final String _fname; - // Type of the field - public final Type _type; - - private Field(String fname, Type type ) { - super(TFLD); - _fname = fname; - _type = type; - } - public static Field make( String fname, Type type ) { - return new Field(fname,type).intern(); - } - - public static final Field TEST = make("test",TypeInteger.ZERO); - public static void gather(ArrayList ts) { ts.add(TEST); } - - @Override Field xmeet( Type that ) { - Field fld = (Field)that; // Invariant - assert _fname.equals(fld._fname); - return make(_fname,_type.meet(fld._type)); - } - - @Override - public Field dual() { return make(_fname,_type.dual()); } - - @Override - public Field glb() { - return make(_fname,_type.glb()); - } - - // Override in subclasses - int hash() { return _fname.hashCode() ^ _type.hashCode(); } - - boolean eq(Type t) { - Field f = (Field)t; - return Utils.eq(_fname,f._fname) && _type==f._type; - } - - - @Override - public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_fname).append(":")); - } - - @Override public String str() { return _fname; } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index bd9cf458..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * These types are part of a Monotone Analysis Framework, - * @see see for example this set of slides. - *

- * The types form a lattice; @see a symmetric complete bounded (ranked) lattice. - *

- * This wild lattice theory will be needed later to allow us to easily beef up - * the analysis and optimization of the Simple compiler... but we don't need it - * now, just know that it is coming along in a later Chapter. - *

g - * One of the fun things here is that while the theory is deep and subtle, the - * actual implementation is darn near trivial and is generally really obvious - * what we're doing with it. Right now, it's just simple integer math to do - * simple constant folding e.g. 1+2 == 3 stuff. - */ - -public class Type { - static final HashMap INTERN = new HashMap<>(); - - // ---------------------------------------------------------- - // Simple types are implemented fully here. "Simple" means: the code and - // type hierarchy are simple, not that the Type is conceptually simple. - static final byte TBOT = 0; // Bottom (ALL) - static final byte TTOP = 1; // Top (ANY) - static final byte TCTRL = 2; // Ctrl flow bottom - static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) - static final byte TSIMPLE = 4; // End of the Simple Types - static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 7; // Whole-memory token - static final byte TMEMPTR = 8; // Memory pointer type - static final byte TSTRUCT = 9; // Structs; tuples with named fields - static final byte TFLD =10; // Fields into struct - - public final byte _type; - - public boolean is_simple() { return _type < TSIMPLE; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; - protected Type(byte type) { _type = type; } - - public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL - public static final Type TOP = new Type( TTOP ).intern(); // ANY - public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl - public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl - public static Type[] gather() { - ArrayList ts = new ArrayList<>(); - ts.add(BOTTOM); - ts.add(CONTROL); - Field.gather(ts); - TypeInteger.gather(ts); - TypeMem.gather(ts); - TypeMemPtr.gather(ts); - TypeStruct.gather(ts); - TypeTuple.gather(ts); - int sz = ts.size(); - for( int i = 0; i < sz; i++ ) - ts.add(ts.get(i).dual()); - return ts.toArray(new Type[ts.size()]); - } - - // Is high or on the lattice centerline. - public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } - - // Strict constant values, things on the lattice centerline. - // Excludes both high and low values - public boolean isConstant() { return false; } - - /** - * Display Type name in a format that's good for IR printer - */ - public StringBuilder typeName( StringBuilder sb) { return print(sb); } - - public Type makeInit() { return null; } - - // ---------------------------------------------------------- - - // Notes on Type interning. - // At the moment it is not easy to reset the interned types - // because we hold static references to several types and these are scattered - // around. This means the INTERN cache will retain all types from - // every run of the Parser. For this to work correctly types must be - // rigorous about defining when they are the same. Also types need to be - // immutable once defined. - // The rationale for interning is performance. - - // Factory method which interns "this" - public T intern() { - T nnn = (T)INTERN.get(this); - if( nnn==null ) - INTERN.put(nnn=(T)this,this); - return nnn; - } - - private int _hash; // Hash cache; not-zero when set. - @Override - public final int hashCode() { - if( _hash!=0 ) return _hash; - _hash = hash(); - if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing - return _hash; - } - // Override in subclasses - int hash() { return _type; } - - @Override - public final boolean equals( Object o ) { - if( o==this ) return true; - if( !(o instanceof Type t)) return false; - if( _type != t._type ) return false; - return eq(t); - } - // Overridden in subclasses; subclass can assume "this!=t" and java classes are same - boolean eq(Type t) { return true; } - - - // ---------------------------------------------------------- - public final Type meet(Type t) { - // Shortcut for the self case - if( t == this ) return this; - // Same-type is always safe in the subclasses - if( _type==t._type ) return xmeet(t); - // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". - if( is_simple() ) return this.xmeet(t ); - if( t.is_simple() ) return t .xmeet(this); - return Type.BOTTOM; // Mixing 2 unrelated types - } - - // Compute meet right now. Overridden in subclasses. - // Handle cases where 'this.is_simple()' and unequal to 't'. - // Subclassed xmeet calls can assert that '!t.is_simple()'. - Type xmeet(Type t) { - assert is_simple(); // Should be overridden in subclass - // ANY meet anything is thing; thing meet ALL is ALL - if( _type==TBOT || t._type==TTOP ) return this; - if( _type==TTOP || t._type==TBOT ) return t; - // 'this' is {TCTRL,TXCTRL} - if( !t.is_simple() ) return BOTTOM; - // 't' is {TCTRL,TXCTRL} - return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; - } - - public Type dual() { - return switch( _type ) { - case TBOT -> TOP; - case TTOP -> BOTTOM; - case TCTRL -> XCONTROL; - case TXCTRL -> CONTROL; - default -> throw Utils.TODO(); // Should not reach here - }; - } - - // ---------------------------------------------------------- - // Our lattice is defined with a MEET and a DUAL. - // JOIN is dual of meet of both duals. - public final Type join(Type t) { - if( this==t ) return this; - return dual().meet(t.dual()).dual(); - } - - // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } - - /** - * Compute greatest lower bound in the lattice - */ - public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; } - - // ---------------------------------------------------------- - // Useful in the debugger, which calls toString everywhere. - // This is a more verbose dev-friendly print. - @Override - public final String toString() { - return print(new StringBuilder()).toString(); - } - - public StringBuilder print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} - - // This is used by error messages, and is a shorted print. - public String str() { return STRS[_type]; } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index 8c77672b..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Integer Type - */ -public class TypeInteger extends Type { - - public final static TypeInteger TOP = make(false, 0); - public final static TypeInteger BOT = make(false, 1); - public final static TypeInteger ZERO= make(true , 0); - - public final boolean _is_con; - - /** - * The constant value or - * if not constant then 1=bottom, 0=top. - */ - public final long _con; - - private TypeInteger(boolean is_con, long con) { - super(TINT); - _is_con = is_con; - _con = con; - } - public static TypeInteger make(boolean is_con, long con) { - return new TypeInteger(is_con,con).intern(); - } - - public static TypeInteger constant(long con) { return make(true, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append(_con); - } - - @Override public String str() { - if( this==TOP ) return "~int"; - if( this==BOT ) return "int"; - return ""+_con; - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append("Int"); - } - - @Override - public boolean isHighOrConst() { return _is_con || _con==0; } - - @Override - public boolean isConstant() { return _is_con; } - - public long value() { return _con; } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeInteger) - TypeInteger i = (TypeInteger)other; // Contract - // BOT wins - if ( this==BOT ) return this; - if ( i ==BOT ) return i ; - // TOP loses - if ( i ==TOP ) return this; - if ( this==TOP ) return i ; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return BOT; - } - - @Override - public Type dual() { - if( isConstant() ) return this; // Constants are a self-dual - return _con==0 ? BOT : TOP; - } - - @Override - public Type glb() { return BOT; } - - @Override public TypeInteger makeInit() { return ZERO; } - - @Override - int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); } - @Override - public boolean eq( Type t ) { - TypeInteger i = (TypeInteger)t; // Contract - return _con==i._con && _is_con==i._is_con; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index d921bac3..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** A token for all memory; field values are not tracked in its type. */ -public class TypeMem extends Type { - private final boolean _high; - private TypeMem(boolean high) { super(TMEM); _high = high; } - public static final TypeMem TOP = new TypeMem(true ).intern(); - public static final TypeMem BOT = new TypeMem(false).intern(); - - public static void gather(ArrayList ts) { ts.add(BOT); } - @Override TypeMem xmeet(Type t) { return BOT; } - @Override public Type dual() { return _high ? BOT : TOP; } - @Override public Type glb() { return BOT; } - @Override int hash() { return _high ? 1 : 2; } - @Override boolean eq(Type t) { return _high==((TypeMem)t)._high; } - @Override public StringBuilder print(StringBuilder sb) { return sb.append(_high ? "MemTop" : "MemBot"); } - @Override public String str() { return print(new StringBuilder()).toString(); } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 6c874a93..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Represents a Pointer to memory. - * - * Null is generic pointer to non-existent memory. - * *void is a non-Null pointer to all possible structs. - * Pointers can be to specific struct types, or a union with Null. - * The distinguished *$BOT ptr represents union of *void and Null. - * The distinguished *$TOP ptr represents the dual of *$BOT. - */ -public class TypeMemPtr extends Type { - // A TypeMemPtr is pair (obj,nil) - // where obj is one of - // (null,TypeStruct). - // where nil is one of - // (true,false) meaning an explicit null is allowed or not - - public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY - public final boolean _nil; - - private TypeMemPtr(TypeStruct obj, boolean nil) { - super(TMEMPTR); - _obj = obj; - _nil = nil; - } - public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } - public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); } - - public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); - public static final TypeMemPtr TOP = BOT.dual(); - public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); - public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null - public static final TypeMemPtr TEST= make(TypeStruct.TEST,false); - public static void gather(ArrayList ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); } - - @Override - Type xmeet(Type t) { - TypeMemPtr that = (TypeMemPtr) t; - return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil); - } - - @Override - public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); } - - @Override - public Type glb() { - if( _obj==null ) return BOT; - return make(_obj.glb(),true); - } - @Override public TypeMemPtr makeInit() { return NULLPTR; } - - @Override - int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); } - - @Override - boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _obj == ptr._obj && _nil == ptr._nil; - } - - // [void,name,MANY]*[,?] - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("PtrBot"); - if( this==TOP ) return sb.append("PtrTop"); - if( this== NULLPTR) return sb.append("null"); - if( this== VOIDPTR) return sb.append("*void"); - return _obj.print(sb.append("*")).append(_nil ? "?" : ""); - } - - @Override public String str() { - if( this==BOT ) return "PtrBot"; - if( this==TOP ) return "PtrTop"; - if( this== NULLPTR) return "null"; - if( this== VOIDPTR) return "*void"; - return "*"+_obj.str()+(_nil ? "?" : ""); - } - -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 44741def..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a struct type. - */ -public class TypeStruct extends Type { - - // For now in terms of the lattice - // a struct type just stays as is - - public final String _name; - public final Field[] _fields; - - private TypeStruct(String name, Field[] fields) { - super(Type.TSTRUCT); - _name = name; - _fields = fields; - } - - public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); } - public static TypeStruct make(String name, ArrayList fields) { return make(name,fields.toArray(new Field[fields.size()])); } - - public static final TypeStruct TOP = make("$TOP",new Field [0]); - public static final TypeStruct BOT = make("$BOT",new Field [0]); - public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); - public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); } - - public int find(String fname) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._fname.equals(fname) ) - return i; - return -1; - } - - @Override - Type xmeet(Type t) { - TypeStruct that = (TypeStruct) t; - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - - // Within the same compilation unit, struct names are unique. If the - // names differ, its different structs. Across many compilation units, - // structs with the same name but different field layouts can be - // interned... which begs the question: - // "What is the meet of structs from two different compilation units?" - // And the answer is: "don't ask". - if( !_name.equals(that._name) ) - return BOT; // It's a struct; that's about all we know - // Now all fields should be the same, so just do field meets - assert _fields.length == that._fields.length; - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = (Field)_fields[i].meet(that._fields[i]); - return make(_name,flds); - } - - @Override - public TypeStruct dual() { - if( this==TOP ) return BOT; - if( this==BOT ) return TOP; - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].dual(); - return make(_name,flds); - } - - // Keeps the same struct, but lower-bounds all fields. - @Override - public TypeStruct glb() { - if( _glb() ) return this; - // Need to glb each field - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].glb(); - return make(_name,flds); - } - private boolean _glb() { - for( Field f : _fields ) - if( f.glb() != f ) - return false; - return true; - } - - @Override - boolean eq(Type t) { - TypeStruct ts = (TypeStruct)t; // Invariant - if( !_name.equals(ts._name) ) - return false; - if( _fields.length!=ts._fields.length ) - return false; - for( int i = 0; i < _fields.length; i++ ) - if( _fields[i] != ts._fields[i] ) - return false; - return true; - } - - @Override - int hash() { - long hash = _name.hashCode(); - for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode(); - return Utils.fold(hash); - } - - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("StructBot"); - if( this==TOP ) return sb.append("StructTop"); - sb.append(_name).append(" {\n"); - for( Field f : _fields ) { - sb.append(" ").append(f._fname).append(":"); - f._type.print(sb); - sb.append(";\n"); - } - return sb.append("}"); - } - - @Override public String str() { - if( this==BOT ) return "StructBot"; - if( this==TOP ) return "StructTop"; - return _name; - } -} diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java deleted file mode 100644 index 0aa019a7..00000000 --- a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -public class TypeTuple extends Type { - - public final Type[] _types; - - private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } - public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } - - private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static void gather(ArrayList ts) { ts.add(TEST); } - - @Override - Type xmeet(Type other) { - TypeTuple tt = (TypeTuple)other; // contract from xmeet - assert _types.length == tt._types.length; - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].meet(tt._types[i]); - return make(ts); - } - - @Override public Type dual() { - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].dual(); - return make(ts); - } - - @Override - public Type glb() { - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].glb(); - return make(ts); - } - - @Override - public StringBuilder print(StringBuilder sb) { - sb.append("["); - for( Type t : _types ) - t.print(sb).append(","); - sb.setLength(sb.length()-1); - sb.append("]"); - return sb; - } - - @Override public String str() { return print(new StringBuilder()).toString(); } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - sb.append("["); - for( Type t : _types ) - t.typeName(sb).append(","); - sb.setLength(sb.length()-1); - sb.append("]"); - return sb; - } - - public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); - public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); - public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); - public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); - - @Override - int hash() { - int sum = 0; - for( Type type : _types ) sum ^= type.hashCode(); - return sum; - } - - @Override - boolean eq( Type t ) { - TypeTuple tt = (TypeTuple)t; // Contract - if( _types.length != tt._types.length ) return false; - for( int i=0; i<_types.length; i++ ) - if( _types[i]!=tt._types[i] ) - return false; - return true; - } - - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java deleted file mode 100644 index 89882b2f..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter01Test { - - @Test - public void testSimpleProgram() { - Parser parser = new Parser("return 1;"); - StopNode stop = parser.parse(); - StartNode start = Parser.START; - ReturnNode ret = (ReturnNode)stop.in(0); - - assertTrue(ret.ctrl() instanceof ProjNode); - Node expr = ret.expr(); - if( expr instanceof ConstantNode con ) { - assertEquals(start,con.in(0)); - assertEquals(TypeInteger.constant(1), con._type); - } else { - fail(); - } - } - - @Test - public void testZero() { - Parser parser = new Parser("return 0;"); - parser.parse(); - StartNode start = Parser.START; - for( Node use : start._outputs ) - if( use instanceof ConstantNode con && con._type instanceof TypeInteger ) - assertEquals(TypeInteger.constant(0),con._type); - } - - @Test - public void testBad1() { - try { - new Parser("ret").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'ret'",e.getMessage()); - } - } - - @Test - public void testBad2() { - try { - new Parser("return 0123;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); - } - } - - @Test - public void testNotBad3() { - // this test used to fail in chapter 1 - assertEquals("return 12;", new Parser("return --12;").parse().print()); - } - - @Test - public void testBad4() { - try { - new Parser("return 100").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected ;: ",e.getMessage()); - } - } - - @Test - public void testNotBad5() { - // this test used to fail in chapter 1 - assertEquals("return -100;", new Parser("return -100;").parse().print()); - } - - @Test - public void testBad6() { - try { - new Parser("return100").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'return100'",e.getMessage()); - } - } - - @Test - public void testBad7() { - try { - new Parser("return 1;}").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, unexpected }",e.getMessage()); - } - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java deleted file mode 100644 index a6075b75..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter02Test { - - @Test - public void testParseGrammar() { - Parser parser = new Parser("return 1+2*3+-5;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - - @Test - public void testAddPeephole() { - Parser parser = new Parser("return 1+2;"); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.print()); - } - - @Test - public void testSubPeephole() { - Parser parser = new Parser("return 1-2;"); - StopNode ret = parser.parse(); - assertEquals("return -1;", ret.print()); - } - - @Test - public void testMulPeephole() { - Parser parser = new Parser("return 2*3;"); - StopNode ret = parser.parse(); - assertEquals("return 6;", ret.print()); - } - - @Test - public void testDivPeephole() { - Parser parser = new Parser("return 6/3;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - - @Test - public void testMinusPeephole() { - Parser parser = new Parser("return 6/-3;"); - StopNode ret = parser.parse(); - assertEquals("return -2;", ret.print()); - } - - @Test - public void testExample() { - Parser parser = new Parser("return 1+2*3+-5;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java deleted file mode 100644 index 609dff76..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter03Test { - - @Test - public void testVarDecl() { - Parser parser = new Parser("int a=1; return a;"); - StopNode ret = parser.parse(); - assertEquals("return 1;", ret.print()); - } - - @Test - public void testVarAdd() { - Parser parser = new Parser("int a=1; int b=2; return a+b;"); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.print()); - } - - @Test - public void testVarScope() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;"); - StopNode ret = parser.parse(); - assertEquals("return 4;", ret.print()); - } - - @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); "); - StopNode ret = parser.parse(); - assertEquals("return 8;", ret.print()); - } - - @Test - public void testSelfAssign() { - try { - new Parser("int a=a; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'a'",e.getMessage()); - } - } - - @Test - public void testRedeclareVar() { - try { - new Parser("int a=1; int b=2; int c=0; int b=3; c=a+b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Redefining name 'b'",e.getMessage()); - } - } - - @Test - public void testBad1() { - try { - new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected }: ",e.getMessage()); - } - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java deleted file mode 100644 index edc62f50..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter04Test { - - @Test - public void testPeephole() { - Parser parser = new Parser("return 1+arg+2; "); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testPeephole2() { - Parser parser = new Parser("return (1+arg)+2;"); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testAdd0() { - Parser parser = new Parser("return 0+arg;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void testAddAddMul() { - Parser parser = new Parser("return arg+0+arg;"); - StopNode ret = parser.parse(); - assertEquals("return (arg*2);", ret.print()); - } - - @Test - public void testPeephole3() { - Parser parser = new Parser("return 1+arg+2+arg+3; "); - StopNode ret = parser.parse(); - assertEquals("return ((arg*2)+6);", ret.print()); - } - - @Test - public void testMul1() { - Parser parser = new Parser("return 1*arg;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void testSub0() { - Parser parser = new Parser("return arg-0;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void test0Sub() { - Parser parser = new Parser("return 0-arg;"); - StopNode ret = parser.parse(); - assertEquals("return (-arg);", ret.print()); - } - - @Test - public void testSubNeg() { - Parser parser = new Parser("return 3--arg;"); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testNegSub() { - Parser parser = new Parser("return -arg-3;"); - StopNode ret = parser.parse(); - assertEquals("return (-(arg+3));", ret.print()); - } - - @Test - public void testVarArg() { - Parser parser = new Parser("return arg; "); - StopNode stop = parser.parse(); - ReturnNode ret = stop.ret(); - assertTrue(ret.in(0) instanceof ProjNode); - assertTrue(ret.in(1) instanceof ProjNode); - assertEquals(1, ((ProjNode)ret.in(1))._idx); - assertTrue(ret.in(2) instanceof ProjNode); - assertEquals(2, ((ProjNode)ret.in(2))._idx); - } - - @Test - public void testConstantArg() { - 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; "); - StopNode ret = parser.parse(); - assertEquals("return 1;", ret.print()); - } - - @Test - public void testCompEq2() { - 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; "); - StopNode ret = parser.parse(); - assertEquals("return 0;", ret.print()); - } - - @Test - public void testCompNEq2() { - 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; "); - 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; "); - StopNode ret = parser.parse(); - assertEquals("return (arg+1);", ret.print()); - } - - @Test - public void testBug3() { - try { - new Parser("inta=1; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'inta'",e.getMessage()); - } - } - - @Test - public void testBug4() { - Parser parser = new Parser("return -arg;"); - StopNode ret = parser.parse(); - assertEquals("return (-arg);", ret.print()); - } - - @Test - public void testBug5() { - Parser parser = new Parser("return arg--2;"); - StopNode ret = parser.parse(); - assertEquals("return (arg--2);", ret.print()); - } -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java deleted file mode 100644 index cf7bd6cf..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter05Test { - - @Test - public void testIfStmt() { - Parser parser = new Parser( -""" -int a = 1; -if (arg == 1) - a = arg+2; -else { - a = arg-3; - -} - -return a;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); - } - - @Test - public void testTest() { - Parser parser = new Parser( -""" -int c = 3; -int b = 2; -if (arg == 1) { - b = 3; - c = 4; -} -return c;""", TypeInteger.BOT); - StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region17,4,3);", ret.toString()); - } - - @Test - public void testReturn2() { - Parser parser = new Parser( -""" -if( arg==1 ) - return 3; -else - return 4; -""", TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("Stop[ return 3; return 4; ]", stop.toString()); - } - - @Test - public void testIfMergeB() { - Parser parser = new Parser( -""" -int a=arg+1; -int b=0; -if( arg==1 ) - b=a; -else - b=a+1; -return a+b;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); - } - - @Test - public void testIfMerge2() { - Parser parser = new Parser( -""" -int a=arg+1; -int b=arg+2; -if( arg==1 ) - b=b+a; -else - a=b+1; -return a+b;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); - } - - @Test - public void testMerge3() { - Parser parser = new Parser( -""" -int a=1; -if( arg==1 ) - if( arg==2 ) - a=2; - else - a=3; -else if( arg==3 ) - a=4; -else - a=5; -return a; -""", TypeInteger.BOT); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); - } - - @Test - public void testMerge4() { - Parser parser = new Parser( -""" -int a=0; -int b=0; -if( arg ) - a=1; -if( arg==0 ) - b=2; -return arg+a+b; -""", TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,2,0));", stop.toString()); - } - - @Test - public void testMerge5() { - Parser parser = new Parser( -""" -int a=arg==2; -if( arg==1 ) -{ - a=arg==3; -} -return a;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region17,3,2));", ret.toString()); - } - - @Test - public void testMergeSameCon() { - Parser parser = new Parser( -""" -int a=arg; -if( arg==1 ) - a=3; -else - a=3; -return a; -"""); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.toString()); - } - - @Test - public void testTrue() { - StopNode stop = new Parser("return true;").parse(); - assertEquals("return 1;",stop.toString()); - } - - @Test - public void testHalfDef() { - try { - new Parser("if( arg==1 ) int b=2; return b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); - } - } - - @Test - public void testHalfDef2() { - try { - new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'b'",e.getMessage()); - } - } - - @Test - public void testRegress1() { - try { - new Parser("if(arg==2) int a=1; else int b=2; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); - } - } - - - @Test - public void testBadNum() { - try { - new Parser("return 1-;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); - } - } - - @Test - public void testKeyword1() { - try { - new Parser("int true=0; return true;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Expected an identifier, found 'true'",e.getMessage()); - } - } - - @Test - public void testKeyword2() { - try { - new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Expected an identifier, found 'else'",e.getMessage()); - } - } - - @Test - public void testKeyword3() { - try { - new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'ififif'",e.getMessage()); - } - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java deleted file mode 100644 index 920e26e3..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ /dev/null @@ -1,294 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter06Test { - - @Test - public void testPeepholeReturn() { - Parser parser = new Parser( -""" -if( true ) return 2; -return 1; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof ProjNode); - } - - @Test - public void testPeepholeRotate() { - Parser parser = new Parser( -""" -int a = 1; -if (arg) - a = 2; -return (arg < a) < 3; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return ((arg 4 ) - break; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testRegress2() { - Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testBreakOutsideLoop() { - try { - new Parser(""" -if(arg <= 10) { - break; - arg = arg + 1; -} -return arg; -""").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("No active loop for a break or continue",e.getMessage()); - } - } - - @Test - public void testRegress3() { - Parser parser = new Parser(""" -while(arg < 10) { - break; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testRegress4() { - Parser parser = new Parser(""" -int a = 1; -while(arg < 10) { - a = a + 1; - if (arg > 2) { - int a = 17; - break; - } -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop8,1,(Phi_a+1)),Add);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof RegionNode); - } - - @Test - public void testRegress5() { - Parser parser = new Parser(""" -int a = 1; -while(1) { - a = a + 1; - if (a<10) continue; - break; -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop8,1,Add)+1);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java deleted file mode 100644 index 1b1920e8..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.Ignore; - -import static org.junit.Assert.*; - -public class Chapter09Test { - - @Test - @Ignore - public void testJig() { - Parser parser = new Parser(""" -int v0=0; -arg=0; -while(v0) { - while(1) if(arg*arg*0==0) {} - while(0) {} - arg=1; -} -return 0; - """); - StopNode stop = parser.parse().iterate(); - } - - @Test - public void testGVN1() { - Parser parser = new Parser( - """ -int x = arg + arg; -if(arg < 10) { - return arg + arg; -} -else { - x = x + 1; -} -return x; - """); - StopNode stop = parser.parse(); - assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); - Assert.assertEquals(2L, Evaluator.evaluate(stop, 1)); - Assert.assertEquals(23L, Evaluator.evaluate(stop, 11)); - } - - @Test - public void testGVN2() { - Parser parser = new Parser( - """ -return arg*arg-arg*arg; - """); - StopNode stop = parser.parse(); - assertEquals("return 0;", stop.toString()); - Assert.assertEquals(0L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist1() { - Parser parser = new Parser( - """ -int step = 1; -while (arg < 10) { - arg = arg + step + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); - Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist2() { - Parser parser = new Parser( - """ -int cond = 0; -int one = 1; -while (arg < 10) { - if (cond) one = 2; - arg = arg + one*3 + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); - Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist3() { - Parser parser = new Parser( - """ -int v1 = 0; -int v2 = 0; -int v3 = 0; -int v4 = 0; -int v5 = 0; -int v6 = 0; -int v7 = 0; -int v8 = 0; -while (arg) { - if (v1) v2 = 1; - if (v2) v3 = 1; - if (v3) v4 = 1; - if (v4) v5 = 1; - if (v5) v6 = 1; - if (v6) v7 = 1; - if (v7) v8 = 1; - arg = arg + v8 + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,(Phi_arg+1));", stop.toString()); - } - - @Test - public void testRegionPeepBug() { - Parser parser = new Parser( - """ -int v0=0; -int v1=0; -while(v1+arg) { - arg=0; - int v2=v0; - while(arg+1) {} - v0=1; - v1=v2; -} - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testWhile0() { - Parser parser = new Parser("while(0) continue; if(0) arg=0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testWhile1() { - Parser parser = new Parser(""" -if(0) while(0) { - int arg=arg; - while(0) {} -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - - @Test - public void testPrecedence() { - Parser parser = new Parser("return 3-1+2;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 4;", stop.toString()); - } - - @Test - public void testSwap2() { - Parser parser = new Parser("return 1+(1+1);"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 3;", stop.toString()); - } - - - @Test - public void testFuzz0() { - Parser parser = new Parser(""" -int one = 1; -int a = 0; -int zero = 0; -while(arg) { - a = -(one + a + 2); - arg = arg + 1; - one = one + zero; -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,(-(Phi_a+3)));", stop.toString()); - } - - @Test - public void testFuzz1() { - Parser parser = new Parser(""" -while(1) {} -while(arg) break; -while(arg) arg=0; -arg=0; -int v0=0!=0<-0; -return -0+0+0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - - @Test - public void testFuzz2() { - Parser parser = new Parser("return 0+-0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testFuzz3() { - Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz4() { - Parser parser = new Parser(""" -while(1) { - arg=0<=0; - if(1<0) while(arg==-0) arg=arg-arg; -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz5() { - Parser parser = new Parser(""" -{ - int v0=0; - while(1) - int v1=0--0; - while(v0) - break; - while(-v0) { - while(0+0+v0) continue; - break; - } - if(-0!=-0+0+v0) while(0+0+0+0) - break; -} -return 0!=0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz6() { - Parser parser = new Parser(""" -int v0=0; -while(0==1) while(v0) - v0=1+v0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz7() { - Parser parser = new Parser(""" -while(1) {} -int v0=0; -while(v0) - {} -int v1=0; -while(1) - v1=1; -return v1+v0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz8() { - Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,arg,(Phi_arg-1));", stop.toString()); - } - - @Test - public void testMeet() { - Type t1 = Type.TOP; - Type t2 = TypeInteger.TOP; - Assert.assertEquals(TypeInteger.TOP, t1.meet(t2)); - Assert.assertEquals(TypeInteger.TOP, t2.meet(t1)); - t1 = Type.BOTTOM; - t2 = TypeInteger.BOT; - Assert.assertEquals(Type.BOTTOM, t1.meet(t2)); - Assert.assertEquals(Type.BOTTOM, t2.meet(t1)); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java deleted file mode 100644 index f39b1e78..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ /dev/null @@ -1,581 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.node.LoadNode; -import com.seaofnodes.simple.node.StoreNode; -import com.seaofnodes.simple.evaluator.Evaluator; -import java.util.BitSet; -import org.junit.Test; - -import java.util.Arrays; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class Chapter10Test { - @Test public void testDeadReferenceReturn() { - for( String body : new String[] { - "return new S; return 0;", - "if(1) return new S; else return 0;" - } ) { - String src = "struct S { int x; }; " + body; - var stop = new Parser(src).parse().iterate(); - org.junit.Assert.assertTrue(body, - stop.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); - } - } - - - - // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. - private static final String NULLABLE_POINT_SOURCE = """ - struct Point { int x; }; - Point point = new Point; - point.x = 42; - Point? p = null; - if (arg) p = point; - """; - - @Test - public void testNullGuards() { - for( String body : new String[] { - "if (p != null) return p.x; return -1;", - "if (null != p) return p.x; return -1;", - "if (!!!!p) return p.x; return -1;", - "if (!!!p) return -1; else return p.x;", - "int b = !!p; if (b) return p.x + b - 1; return -1;" - } ) { - StopNode stop = new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); - assertEquals(body,-1L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); - assertEquals(body,42L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); - } - } - - @Test - public void testNullGuardErrors() { - for( String body : new String[] { - "return p.x;", - "if (p == null) return p.x; return -1;", - "if (!!point) return p.x; return -1;", - "if (!!p) { int x = p.x; } return p.x;" - } ) { - try { - new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); - fail(body); - } catch( RuntimeException e ) { - // Known null is rejected during parsing in these chapters. - String expected = body.startsWith("if (p == null)") - ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'"; - assertEquals(body,expected,e.getMessage()); - } - } - } - - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -int a = arg/3; -int b = arg*5; -int x = arg*7; -int y = arg/11; -int p; int g; int h; -if( (arg/13)==0 ) { - p = x + y; - g = x; - h = y; -} else { - p = a + b; - g = a; - h = b; -} -int r = g+h; -return p-r; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testStruct() { - Parser parser = new Parser(""" -struct Bar { - int a; - int b; -} -struct Foo { - int x; -} -Foo? foo = null; -Bar bar = new Bar; -bar.a = 1; -bar.a = 2; -return bar.a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - } - - @Test - public void testExample() { - Parser parser = new Parser(""" -struct Vector2D { int x; int y; } -Vector2D v = new Vector2D; -v.x = 1; -if (arg) - v.y = 2; -else - v.y = 3; -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new Vector2D;", stop.toString()); - } - - @Test - public void testBug() { - Parser parser = new Parser(""" -struct s0 { - int v0; -} -s0? v1=null; -int v3=v1.zAicm; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); } - } - - @Test - public void testBug2() { - Parser parser = new Parser(""" -struct s0 { int v0; } -arg=0+new s0.0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } - } - - @Test - public void testLoop() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = new Bar; -while (arg) { - bar.a = bar.a + 2; - arg = arg + 1; -} -return bar.a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,(Phi_$mem+2));", stop.toString()); - } - - @Test - public void testIf() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = new Bar; -if (arg) bar = null; -bar.a = 1; -return bar.a; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); } - } - - @Test - public void testIf2() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar? bar = null; -if (arg) bar = new Bar; -bar.a = 1; -return bar.a; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); } - } - - @Test - public void testIf3() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = null; -if (arg) bar = null; -bar.a = 1; -return bar.a; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); } - } - - @Test - public void testIfOrNull() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -if( bar ) bar.a = 1; -return bar; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region15,null,new Bar);", stop.toString()); - } - - @Test - public void testIfOrNull2() { - Parser parser = new Parser( -""" -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -int rez = 3; -if( !bar ) rez=4; -else bar.a = 1; -return rez; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,4,3);", stop.toString()); - } - - @Test - public void testWhileWithNullInside() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0? v0 = new s0; -int ret = 0; -while(arg) { - ret = v0.v0; - v0 = null; - arg = arg - 1; -} -return ret; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); } - } - - @Test - public void testRedeclareStruct() { - Parser parser = new Parser(""" -struct s0 { - int v0; -} -s0? v1=new s0; -s0? v1; -v1=new s0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); } - } - - @Test - public void testIter() { - // Build and use an iterator - Parser parser = new Parser( -""" -struct Iter { - int x; - int len; -} -Iter i = new Iter; -i.len = arg; -int sum=0; -while( i.x < i.len ) { - sum = sum + i.x; - i.x = i.x + 1; -} -return sum; -"""); - StopNode stop = parser.parse().iterate(); - // The preceding len store keeps the initial x load on the bulk chain. - assertEquals("return Phi(Loop13,0,(Phi(Loop,.x,(Phi_x+1))+Phi_sum));", stop.toString()); - assertEquals(10L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,5)); - } - - - - - @Test - public void test1() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -while(arg) { - s0 v0 = new s0; - v0.v0 = arg; - arg = arg-1; - if (arg==5) ret=v0; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString()); - } - - @Test - public void test2() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -s0 v0 = new s0; -while(arg) { - v0.v0 = arg; - arg = arg-1; - if (arg==5) ret=v0; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); - } - - - @Test - public void test3() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -while(arg < 10) { - s0 v0 = new s0; - if (arg == 5) ret=v0; - arg = arg + 1; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString()); - } - - @Test - public void testBug3() { - Parser parser = new Parser(""" -struct s0 { - int f0; -} -if(0>=0) return new s0; -return new s0; -int v0=null.f0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); } - } - - @Test - public void testBug4() { - Parser parser = new Parser(""" -if(0) { - while(0) if(arg) continue; - int v0=0; - while(1) { - int arg=-arg; - v0=arg; - } -} - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testBug5() { - Parser parser = new Parser(""" -struct s0 { - int f0; -} -if(0) return 0; -else return new s0; -if(new s0.f0) return 0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return new s0;", stop.toString()); - } - - @Test - public void testBug6MissedWorklist() { - Parser parser = new Parser(""" -while(0) {} -int v4=0; -while(0 0) { - s.x = s.x + 1; - int j = 3; - while (j > 0) { - j = j - 1; - if (j == 1) continue; - s.y = s.y + arg; - if (arg == 2) break; - s.z = s.z + 1; - } - arg = arg - 1; - } - return s; - """).parse().iterate(); - for (int arg=0; arg<7; arg++) { - var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) - com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); - org.junit.Assert.assertArrayEquals(new Object[] { - 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0) - }, obj.fields()); - } - } - - @Test public void testMemoryAtEarlyReturns() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S s = new S; - s.x = 3; s.y = 5; s.z = 7; - if (arg) { s.x = 11; s.z = 13; return s; } - s.y = 17; - return s; - """).parse().iterate(); - for (int arg=0; arg<2; arg++) { - var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) - com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); - org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields()); - } - } - - @Test public void testDropLoads() { - StopNode stop = new Parser(""" - struct S { int x; } - S a = new S; S b = new S; - a.x = arg; b.x = arg+1; - S p = a; S q = b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) v=p.x; else v=q.x; - return v; - """).parse().iterate(); - assertEquals(1,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals(-1L,Evaluator.evaluate(stop,-1)); - assertEquals(1L,Evaluator.evaluate(stop,0)); - assertEquals(3L,Evaluator.evaluate(stop,3)); - } - - @Test public void testDropStores() { - StopNode stop = new Parser(""" - struct S { int x; } - S s = new S; - if (arg) s.x=arg+1; else s.x=arg+2; - return s; - """).parse().iterate(); - assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); - assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); - assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); - } - - @Test public void testKeepReadsBeforeWrites() { - StopNode stop = new Parser(""" - struct S { int x; int y; } - S a = new S; S b = new S; - a.x=arg; b.x=arg+1; - S p=a; S q=b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) { v=p.x; p.x=41; p.y=5; } - else { v=q.x; q.x=42; } - return v; - """).parse().iterate(); - assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals(-1L,Evaluator.evaluate(stop,-1)); - assertEquals(1L,Evaluator.evaluate(stop,0)); - assertEquals(3L,Evaluator.evaluate(stop,3)); - } - - private static int countMemoryNodes(Node n, Class kind, BitSet seen) { - if (n==null || seen.get(n._nid)) return 0; - seen.set(n._nid); - int cnt=kind.isInstance(n) ? 1 : 0; - for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); - return cnt; - } -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java deleted file mode 100644 index 019140ad..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.fuzzer.Fuzzer; -import org.junit.Test; -import org.junit.Ignore; - - -import static org.junit.Assert.assertTrue; - -/** - * Normal tests run chapter-local regression seeds. Open failures and exploratory - * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore). - * Seeds depend on this chapter's generator; do not copy them between chapters. - */ -public class FuzzerWrap { - - private static final long[] REGRESSION_SEEDS = { - }; - - private static final long[] OPEN_FAILING_SEEDS = { - }; - - @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); } - @Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); } - - private static void fuzzPeepsSeeds(long... seeds) { - var fuzzer = new Fuzzer(); - for (long seed : seeds) - fuzzer.fuzzPeepsRegression(seed); - } - - @Test - @Ignore - public void fuzzPeepsLarge() { - var fuzzer = new Fuzzer(); - for (int i=0; i<1000000; i++) - fuzzer.fuzzPeeps(i); - assertTrue(fuzzer.noExceptions()); - } - - - @Test - @Ignore - public void fuzzPeepTiming() { - var fuzzer = new Fuzzer(); - int max_nid=0; - for (int i=0; i<1000000; i++) - max_nid = fuzzer.fuzzPeepTiming(i, max_nid); - } -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java deleted file mode 100644 index ad595822..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SchedulerTest { - - private static void assertObj(Object obj, String name, Object... fields) { - assertEquals(Evaluator.Obj.class, obj.getClass()); - var o = (Evaluator.Obj)obj; - assertEquals(name, o.struct()._name); - Assert.assertArrayEquals(fields, o.fields()); - } - - @Test - @Ignore - public void testJig() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v=new S; -v.f = 2; -int i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse();//.iterate(); - var eval = new Evaluator(stop); - assertEquals(2L, eval.evaluate(1, 10)); - } - - @Test - public void testStoreInIf() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v0=new S; -if(arg) v0.f=1; -return v0; -"""); - StopNode stop = parser.parse().iterate(); - var eval = new Evaluator(stop); - assertObj(eval.evaluate(0, 10), "S", 0L); - assertObj(eval.evaluate(1, 10), "S", 1L); - } - - @Test - public void testStoreInIf2() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - 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/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java deleted file mode 100644 index c9eb2747..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.type.*; -import org.junit.Assert; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class TypeTest { - - // Test basic properties and GLB - @Test - public void testTypeAdHoc() { - TypeStruct s1 = TypeStruct.make("s1", new Field[]{ - Field.make("a", TypeInteger.BOT), - Field.make("b", TypeInteger.BOT) }); - TypeStruct s2 = TypeStruct.make("s2", new Field[]{ - Field.make("a", TypeInteger.BOT), - Field.make("b", TypeInteger.BOT) }); - Assert.assertEquals(s1, s1.glb()); - Assert.assertNotEquals(s1, s1.dual()); - Assert.assertEquals(s1, s1.dual().glb()); - - assertEquals(TypeStruct.BOT, s1.meet(s2)); - assertEquals(TypeMem.BOT, TypeMem.TOP.meet(TypeMem.BOT)); - assertEquals(TypeMem.TOP, TypeMem.BOT.dual()); - assertEquals(TypeMem.BOT, TypeMem.TOP.dual()); - - TypeMemPtr ptr1 = TypeMemPtr.make(s1); - Assert.assertEquals(s1, ptr1._obj); - TypeMemPtr ptr2 = TypeMemPtr.make(s2); - Assert.assertEquals(s2, ptr2._obj); - - TypeMemPtr ptr1nil = TypeMemPtr.make(s1, true); - Assert.assertEquals(s1, ptr1nil._obj); - Assert.assertTrue(ptr1nil._nil); - Assert.assertFalse(ptr1nil._obj==null); - TypeMemPtr ptr2nil = TypeMemPtr.make(s2, true); - Assert.assertEquals(s2, ptr2nil._obj); - - Assert.assertNotEquals(ptr1, ptr2); - Assert.assertNotEquals(ptr1, ptr1.glb()); - Assert.assertEquals(ptr1nil, ptr1.glb()); - - Assert.assertEquals(ptr1, ptr1.dual().dual()); - Assert.assertEquals(ptr1.glb(), ptr1.dual().glb()); - Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil)); - Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR)); - - TypeMemPtr TOP = TypeMemPtr.TOP; - TypeMemPtr BOT = TypeMemPtr.BOT; - TypeMemPtr PTR = TypeMemPtr.VOIDPTR; - TypeMemPtr NULL = TypeMemPtr.NULLPTR; - Type PTR_meet_NULL = NULL.meet(PTR); - Assert.assertEquals(BOT, PTR_meet_NULL); - Type ptr1_meet_ptr2 = ptr1.meet(ptr2); - Assert.assertEquals(PTR, ptr1_meet_ptr2); - Type NULL_join_ptr1 = NULL.join(ptr1); - Assert.assertEquals(TOP, NULL_join_ptr1); - Type NULL_join_PTR = PTR.join(NULL); - Assert.assertEquals(TOP, NULL_join_PTR); - Type ptr1_dual = ptr1.dual(); - Type nullableptr1_dual = ptr1nil.dual(); - } - - // Test theoretical properties. - // This is a symmetric complete bounded (ranked) lattice. - // Also the meet is commutative and associative. - // The lattice has a dual (symmetric), and join is ~(~x meet ~y). - // See https://en.wikipedia.org/wiki/Lattice_(order). - @Test - public void testLatticeTheory() { - Type[] ts = Type.gather(); - - // Confirm commutative & complete - for( Type t0 : ts ) - for( Type t1 : ts ) { - check_commute (t0,t1); - check_symmetric(t0,t1); - } - - // Confirm associative - for( Type t0 : ts ) - for( Type t1 : ts ) - for( Type t2 : ts ) - assoc(t0,t1,t2); - - // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C) - for( Type t0 : ts ) - for( Type t1 : ts ) - if( t0.isa(t1) ) - for( Type t2 : ts ) { - Type t02 = t0.join(t2); - Type t12 = t1.join(t2); - Type mt = t02.meet(t12); - assertSame(mt,t12); - } - } - - // By design in meet, args are already flipped to order _type, which forces - // symmetry for things with badly ordered _type fields. The question is - // still interesting for other orders. - private static void check_commute( Type t0, Type t1 ) { - if( t0==t1 ) return; - if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order - Type mta = t0.meet(t1); - Type mtb = t1.meet(t0); // Reverse args and try again - assertSame(mta,mtb); - } - - // A & B = MT - // Expect: ~A & ~MT == ~A - // Expect: ~B & ~MT == ~B - private static void check_symmetric( Type t0, Type t1 ) { - if( t1==t0 ) return; - Type mt = t0.meet(t1); - Type ta = mt.dual().meet(t1.dual()); - Type tb = mt.dual().meet(t0.dual()); - assertSame(ta,t1.dual()); - assertSame(tb,t0.dual()); - } - - private static void assoc( Type t0, Type t1, Type t2 ) { - Type t01 = t0 .meet(t1 ); - Type t12 = t1 .meet(t2 ); - Type t01_2 = t01.meet(t2 ); - Type t0_12 = t0 .meet(t12); - assertSame(t01_2,t0_12); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java deleted file mode 100644 index bddcc596..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.seaofnodes.simple.evaluator; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Field; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.type.TypeStruct; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Objects; - -public class Evaluator { - - private static final Object MEMORY = new Object(); - - public record Obj(TypeStruct struct, Object[] fields) { - @Override - public String toString() { - var sb = new StringBuilder(); - sb.append("Obj<").append(struct._name).append("> {"); - for(int i=0; i= 0 ) return idx; - throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name); - } - - public enum Status { - TIMEOUT, - FALLTHROUGH - } - - /** - * Find the start node from some node in the graph or null if there is no start node - */ - private static StartNode findStart(BitSet visit, Node node) { - if (node == null) return null; - if( visit.get(node._nid) ) return null; - visit.set(node._nid); - StartNode start = node instanceof StartNode ? (StartNode) node : null; - for( Node def : node._inputs ) { - var res = findStart(visit,def); - if( res != null ) start = res; - } - for( Node use : node._outputs ) { - var res = findStart(visit,use); - if( res != null ) start = res; - } - return start; - } - - private final Object[] values; - private final ArrayList phiCache = new ArrayList<>(); - private final StartNode start; - private final Scheduler.Block startBlock; - - public Evaluator(Node graph) { - var visited = new BitSet(); - this.start = findStart(visited, graph); - if (this.start == null) { - this.values = null; - this.startBlock = null; - return; - } - this.startBlock = Scheduler.schedule(this.start); - this.values = new Object[visited.length()]; - } - - private long div(DivNode div) { - long in2 = vall(div.in(2)); - return in2 == 0 ? 0 : vall(div.in(1)) / in2; - } - - private Object alloc(NewNode alloc) { - var type = ((TypeMemPtr)alloc.compute())._obj; - return new Obj(type, new Object[type._fields.length]); - } - - private Object load(LoadNode load) { - var from = (Obj)val(load.in(2)); - var idx = getFieldIndex(from.struct, load); - return from.fields[idx]; - } - - private Object store(StoreNode store) { - var to = (Obj)val(store.in(2)); - var val = val(store.in(3)); - var idx = getFieldIndex(to.struct, store); - to.fields[idx] = val; - return null; - } - - private static boolean isTrue(Object obj) { - if (obj == null) return false; - if (obj instanceof Number n) return n.longValue() != 0; - return true; - } - - private Object val(Node node) { - return values[node._nid]; - } - - private long vall(Node node) { - var v = val(node); - if (v instanceof Number n) return n.longValue(); - throw new AssertionError("Not a long " + v); - } - - private Object cons(ConstantNode cons) { - var type = cons.compute(); - if (type instanceof TypeInteger i) return i.value(); - assert type instanceof TypeMemPtr; - return null; - } - - private Object exec(Node node) { - return switch (node) { - case ConstantNode cons -> cons(cons); - case AddNode add -> vall(add.in(1)) + vall(add.in(2)); - case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; - case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; - case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; - case DivNode div -> div(div); - case MinusNode minus -> -vall(minus.in(1)); - case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); - case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; - case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); - case CastNode cast -> val(cast.in(1)); - case LoadNode load -> load(load); - case StoreNode store -> store(store); - case NewNode alloc -> alloc(alloc); - case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; - default -> throw new AssertionError("Unexpected node " + node); - }; - } - - /** - * Run the graph until either a return is found or the number of loop iterations are done. - */ - public Object evaluate(long parameter, int loops) { - if (start == null) return Status.TIMEOUT; - var s = new Object[start.compute()._types.length]; - s[1] = MEMORY; - s[2] = parameter; - values[start._nid] = s; - int i=0; - Scheduler.Block block = this.startBlock; - for(;;) { - for(; i 0 && region.nIns() > exit; - block = block.next()[0]; - assert block != null; - for (; i < block.nodes().length; i++) { - if (!(block.nodes()[i] instanceof PhiNode)) break; - phiCache.add(val(block.nodes()[i].in(exit))); - } - for (i=0; i - *
- * This scheduler is used for tests and is not the final scheduler. - *
- *
- * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. - * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy - * this scheduler might place initialization code in loops. - *
- * We schedule late here since memory edges have some implicit information. There can only be one - * memory edge (for a field) at a time. However, before if statements this might split. - * When scheduling early a store might be moved out of the loop but then there are two edges alive. - * The one through the store node and the adjacent one. This is an invalid schedule and instead of - * handling this case a late scheduler does not have this problem as memory edges are combined - * through phi nodes. - *
- *
- * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. - * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes - * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. - */ -public class Scheduler { - - /** - * A basic block with a schedule of the containing nodes. - * - * @param nodes The nodes in the final schedule of this block - * @param exit The exit node of this block. - * @param exitId In case the next block is a region this gives the entry id into the region. - * @param next The next blocks. - */ - public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} - - /** - * Definition of a block in the building process. - */ - private static class BasicBlock { - - /** - * Dominator of this block - */ - final BasicBlock dom; - /** - * Id of this block where a.depth < b.depth means that a is before b or - * both are in different branches. - */ - final int depth; - /** - * Previous blocks - */ - final BasicBlock[] prev; - /** - * Entry to this block - */ - final Node entry; - /** - * Schedules node in reverse order. - */ - final ArrayList reverseSchedule = new ArrayList<>(); - - /** - * Initialize the block. - * @param entry The node that started this block - * @param depth The depth of this node. All previous nodes need to have a lower value. - * @param prev The previous blocks. - */ - BasicBlock(Node entry, int depth, BasicBlock... prev) { - BasicBlock dom = null; - int max = -1; - for(var bb: prev) { - max = Math.max(max, bb.depth); - dom = dom==null?bb:dom(dom, bb); - } - assert depth > max; - this.dom = dom; - this.depth = depth; - this.entry = entry; - this.prev = prev; - } - - } - - /** - * Ancillary data for nodes used during the scheduling process. - */ - private static class NodeData { - - /** - * The node this ancillary data is used for. - */ - final Node node; - - /** - * Number of alive users not yet scheduled. - */ - int users = 1; - - /** - * Block into which the node should be placed so far. - * This will be updated with every placed use. - */ - BasicBlock block; - - /** - * Initialize ancillary data for a node - * @param node The node this ancillary data is associated with. - */ - NodeData(Node node) { - this.node = node; - } - - } - - /** - * Ancillary data for nodes. - */ - private final IdentityHashMap data = new IdentityHashMap<>(); - /** - * List of nodes which can be placed since all users are placed. - */ - private final Stack scheduleQueue = new Stack<>(); - - /** - * Get the ancillary data for an alive node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node. - */ - NodeData d(Node node) { - return od(node).orElseThrow(AssertionError::new); - } - - /** - * Get the ancillary data for a potentially dead node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node an alive node or - * Optional.empty() for null or dead nodes. - */ - Optional od(Node node) { - return Optional.ofNullable(data.get(node)); - } - - /** - * Checks a node for validity - * @param data The node to check. - * @return true if all placed inputs are before this node. - */ - private boolean isValid(NodeData data) { - return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d); - } - - /** - * Checks that node is not XCtrl. - * @param node The node to check. - * @return true if the node is not XCtrl. - */ - private static boolean isNotXCtrl(Node node) { - return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); - } - - /** - * Return the dominator of a and b - * @param a Block a - * @param b Block b - * @return The dominator of a and b - */ - private static BasicBlock dom(BasicBlock a, BasicBlock b) { - while (a!=b) { - if (a.depth >= b.depth) a=a.dom; - if (b.depth > a.depth) b=b.dom; - } - return a; - } - - /** - * Return if a is before b. - * @param a Block a - * @param b Block b - * @return true is a is before b. - */ - private static boolean isBefore(BasicBlock a, BasicBlock b) { - while (b.depth > a.depth) { - while (b.dom.depth > a.depth) b = b.dom; - for (int i=0; i{ - // before might be in a different branch. So check this case with isBefore - // and only refine the placement if it is not in a different branch. - if (isBefore(d.block, data.block)) refinePlacement(data, d.block); - }); - } - - /** - * Schedule all nodes not yet scheduled. - */ - private void doSchedule() { - while (!scheduleQueue.isEmpty()) { - var data = scheduleQueue.pop(); - assert !isPinnedNode(data); - - assert data.block != null; - assert data.users == 0; - - if (data.node instanceof LoadNode l) { - assert isValid(data); - // Handle anti-deps of load nodes. - // At this point all anti-dep nodes are scheduled, - // but they did not refine the placement - // so do that now. - var mem = l.in(1); - for(var out : mem._outputs) { - if (out instanceof PhiNode p) { - var r = p.in(0); - for (int i = 1; i < p.nIns(); i++) { - if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); - } - } else if (!(out instanceof LoadNode)) { - optionalRefinePlacement(data, out); - } - } - } - - assert isValid(data); - data.block.reverseSchedule.add(data.node); - - for(var in:data.node._inputs) { - if (in!=null) update(d(in), data.block); - } - if (data.node instanceof StoreNode s) { - // Store nodes have anti-deps to load nodes. - // So decrease the uses of these loads when the store is placed. - for (var out: s.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(this::decUsers); - } - } - } - - // Now all nodes should be placed and have a block assigned - assert this.data.values().stream().noneMatch(d->d.block==null); - } - - /** - * Decrement the not-yet-placed users of a block. - * @param data The node for which the users should be decremented. - */ - private void decUsers(NodeData data) { - assert data.users > 0; - if (--data.users == 0) { - assert data.block != null; - assert isValid(data); - - // When all users are gone this node can be scheduled. - scheduleQueue.push(data); - } - } - - /** - * Update the placement of a node - * @param data The node to update - * @param block The block before which the node should happen. - */ - private void update(NodeData data, BasicBlock block) { - assert block != null; - if (data.users == 0) { - assert isPinnedNode(data); - return; - } - refinePlacement(data, block); - decUsers(data); - } - - /** - * Checks if a CFG node is ready to be placed. This is used for regions joining branches. - * @param node The CFG node to check - * @return true if all parents of a CFG node are placed. - */ - private boolean isCFGNodeReady(Node node) { - if (node instanceof LoopNode l) { - assert d(l.in(1)).block != null; - } else if (node instanceof RegionNode r) { - for(int i=1; id.block==null).orElse(false)) return false; - } - } else { - assert d(node.in(0)).block != null; - } - return true; - } - - /** - * Schedule all phi nodes. - * @param phiQueue List of all the phi nodes to schedule. - */ - private void schedulePhis(Stack phiQueue) { - while(!phiQueue.empty()) { - var data = phiQueue.pop(); - var phi = (PhiNode) data.node; - var r = phi.in(0); - data.block = d(r).block; - for(int i=1; iupdate(d(in), d.block)); - } - } - } - - /** - * Build the control flow graph. - * @param start The start node. - */ - private void doBuildCTF(StartNode start) { - var queue = new Stack(); - var phiQueue = new Stack(); - queue.push(d(start)); - int depth = 0; - while (!queue.isEmpty()) { - var data = queue.pop(); - var node = data.node; - assert node.isCFG(); - BasicBlock block; - switch (node) { - case StartNode s: - block = new BasicBlock(s, depth++); - break; - case LoopNode l: - block = new BasicBlock(l, depth++, d(l.in(1)).block); - break; - case RegionNode r: - var prev = new ArrayList(); - for(int i=1; iprev.add(d.block)); - } - block = new BasicBlock(r, depth++, prev.toArray(BasicBlock[]::new)); - break; - case IfNode i: - block = d(i.in(0)).block; - break; - case ReturnNode r: - block = d(r.in(0)).block; - break; - default: - block = new BasicBlock(node, depth++, d(node.in(0)).block); - } - data.block = block; - if (node instanceof RegionNode r) { - // Regions might have phis which need to be scheduled. - // Put them on a list for later scheduling. - for (var out:r._outputs) { - if (out instanceof PhiNode p) { - var d = this.data.get(p); - if (d!=null) { - d.users=0; - phiQueue.push(d); - } - } - } - } - if (!(node instanceof ReturnNode)) - for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); - var b = block; - for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); - } - - // Schedule all phi nodes. - schedulePhis(phiQueue); - } - - /** - * Mark a node alive and create ancillary data for it. - * @param queue List into which this node should be placed if it needs to be visited. - * @param node Node which should be marked alive. - * @param cfg If this node is a CFG node. - */ - private void markAlive(Stack queue, Node node, boolean cfg) { - var nd = od(node).orElse(null); - if (nd != null) { - // Node was already visited, just increase the users. - if (nd.users>0) nd.users++; - return; - } - assert node.isCFG() == cfg; - assert isNotXCtrl(node); - nd = new NodeData(node); - if (cfg) nd.users=0; - data.put(node, nd); - queue.push(nd); - } - - /** - * Mark all alive nodes. - * First all CFG nodes are marked, then all other nodes. - * @param node THe start node. - */ - private void doMarkAlive(Node node) { - var cfgQueue = new Stack(); - var dataQueue = new Stack(); - var mem = new Stack(); - markAlive(cfgQueue, node, true); - // Mark all CFG nodes. - while (!cfgQueue.isEmpty()) { - var data = cfgQueue.pop(); - node = data.node; - assert node.isCFG(); - if (!(node instanceof ReturnNode)) { - for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); - } - for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); - } - // Mark all other nodes. - while (!dataQueue.isEmpty()) { - var data = dataQueue.pop(); - node = data.node; - assert !node.isCFG(); - if (node instanceof PhiNode phi) { - var r = phi.in(0); - for (int i=1; id.users++); - } - } - } - - /** - * Helper function to append the nodes of a block to an array of nodes - * @param arr The array the nodes of a block should be appended to. - * @param node A potentially separator node - * @param block The block from which the nodes should be appended. - * @return The combined new array. - */ - private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { - var idx = arr.length; - arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); - if (node != null) arr[idx++] = node; - for(int i=block.reverseSchedule.size()-1;i>=0;i--) { - arr[idx++] = block.reverseSchedule.get(i); - } - return arr; - } - - /** - * Find the CFG output of a node - * @param node The node - * @return THe CFG output of the node. - */ - private static Node findSingleCFGOut(Node node) { - if (node instanceof StartNode) { - for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; - return null; - } - assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; - for(var n:node._outputs) if(n.isCFG()) return n; - return null; - } - - /** - * Build the final data structure after all the scheduling happened. - * @param start The start node - * @return The final schedule. - */ - private Block build(Node start) { - var blocks = new IdentityHashMap(); - var queue = new Stack(); - queue.push(d(start)); - - // Visit all CFG nodes and create blocks for them. - // This can combine blocks. - while(!queue.isEmpty()) { - var data = queue.pop(); - var first = data.node; - Node prev; - var last = first; - Node[] arr = new Node[0]; - if (first instanceof RegionNode) { - var nodes = new ArrayList(); - for(var out: first._outputs) { - if (out instanceof PhiNode) { - if(od(out).isPresent()) nodes.add(out); - } - } - arr = nodes.toArray(arr); - } - arr = appendNodes(arr, null, data.block); - assert !(first instanceof IfNode); - for(;;) { - prev = last; - last = findSingleCFGOut(last); - if (last == null) break; - if (last instanceof RegionNode) { - if (blocks.get(last) == null && last != first) - queue.push(d(last)); - break; - } - if (last instanceof ReturnNode) break; - if (last instanceof IfNode if_) { - for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out)); - break; - } - data = d(last); - arr = appendNodes(arr, data.node, data.block); - } - blocks.put(first, switch(last) { - case null -> new Block(arr, null, -1, new Block[0]); - case IfNode i -> new Block(arr, i, -1, new Block[2]); - case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); - case ReturnNode r -> new Block(arr, r, -1, new Block[0]); - default -> throw new AssertionError("Unexpected block exit node"); - }); - } - // Update the next pointer of all blocks. - for (var block: blocks.values()) { - switch(block.exit) { - case null: break; - case IfNode i: - for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); - break; - case RegionNode r: - block.next[0] = blocks.get(r); - break; - default: - assert block.exit instanceof ReturnNode; - } - } - // And return the block for the start node. - return blocks.get(start); - } - - /** - * Create a schedule for the program reachable from start. - * @param start The start node - * @return The final schedule. - */ - public static Scheduler.Block schedule(StartNode start) { - var scheduler = new Scheduler(); - scheduler.doMarkAlive(start); - scheduler.doBuildCTF(start); - scheduler.doSchedule(); - return scheduler.build(start); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java deleted file mode 100644 index 7dcb88e5..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.Random; -import java.util.function.Consumer; - -/** - * Implementation of the fuzzer. - * Random testcases are generated using the ScriptGenerator. - * This will generate a script similar to how the parser parses it but instead of parsing it will - * randomly choose a variation the parser would parse and generate it. - * These scripts are the parsed by the parser and all exceptions are caught and filtered - * to only show one occurrence of one problem. This includes script which generate different - * results when graphs optimized with different worklist seeds are executed. - * To aid debugging scripts that cause errors are then reduced by applying rules and checking - * that the same issue persists. - */ -public class Fuzzer { - - private static final int EVAL_TIMEOUT = 1000; - - /** - * List of exceptions already encountered. - * This is used to filter new exceptions and discard them if they are already found. - */ - private final ArrayList exceptions = new ArrayList<>(); - - /** - * Filter and record an exception in a script. - * @param e The exception caused by the script - * @param script The script causing the exception - * @param reproducer Reproducer which should be used to reproduce the test case. - */ - private void recordException(Throwable e, String script, Consumer reproducer, long seed) { - for (var ex : exceptions) { - if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; - } - exceptions.add(e); - System.out.println("========== Stack =========="); - e.printStackTrace(System.out); - System.out.println("========== Seed =========="); - System.out.println(seed); - System.out.println("========== Code ==========="); - System.out.println(script); - System.out.println("========= Reduced ========="); - System.out.println(Reducer.reduce(script, e, reproducer)); - System.out.println("==========================="); - System.out.flush(); - } - - private static boolean neq(Object a, Object b) { - if (Objects.equals(a, b)) return false; - if (a instanceof Evaluator.Obj ea && b instanceof Evaluator.Obj eb) { - if (ea.struct() != eb.struct()) return true; - for(int i=0;irunCheck(s, valid), seed); - } - } - - /** - * Run one test with the given seed, and check for peephole counts. - * @param seed The seed to use for generating this test case - */ - public int fuzzPeepTiming(long seed, int max_nids) { - var rand = new Random(seed); - var sb = new StringBuilder(); - new ScriptGenerator(rand, sb, true).genProgram(); - try { - var parser = new Parser(sb.toString()); - - var stop = parser.parse(); - int parse_peeps= Node.ITER_CNT; - int parse_nops = Node.ITER_NOP_CNT; - double parse_nop_ratio = (double)parse_nops/parse_peeps; - int nids = Node.UID(); - if( nids <= max_nids ) return max_nids; - - double parse_peeps_per_node = (double)parse_peeps/nids; - - stop.iterate(); - int iter_peeps= Node.ITER_CNT; - int iter_nops = Node.ITER_NOP_CNT; - double iter_nop_ratio = (double)iter_nops/iter_peeps; - double iter_peeps_per_node = (double)iter_peeps/nids; - - System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n", - seed, - parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node, - iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node); - return nids; - } catch (Throwable e) { - // ignore exceptions, let the normal fullPeeps catch and reduce, - // we're interested in valid program optimizations - } - return max_nids; - } - - - /** - * Run one test with the given seed. - * @param seed The seed to use for generating this test case - */ - public void fuzzPeeps(long seed) { - var rand = new Random(seed); - var sb = new StringBuilder(); - var valid = new ScriptGenerator(rand, sb, true).genProgram(); - check(sb.toString(), valid, seed); - } - - - /** - * Check that no exceptions happened. - * @return true if no exception happened - */ - public boolean noExceptions() { - return exceptions.isEmpty(); - } - - /** Run a fixed chapter-local seed without reducing or swallowing failures. */ - public void fuzzPeepsRegression(long seed) { - var rand = new Random(seed); - var sb = new StringBuilder(); - var valid = new ScriptGenerator(rand, sb, true).genProgram(); - try { - runCheck(sb.toString(), valid); - } catch (Throwable e) { - AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed); - ae.initCause(e); - throw ae; - } - } -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java deleted file mode 100644 index f947a234..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -import java.io.OutputStream; -import java.io.PrintStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; -import java.util.BitSet; - -/** - * Some utilities for the fuzzer. - */ -class FuzzerUtils { - - /** - * Get the val of a private field - */ - @SuppressWarnings("unchecked") - public static T getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { - return (T) (obj instanceof Class clz ? getField(clz, name).get(null) : getField(obj.getClass(), name).get(obj)); - } - - /** - * Get access to a private field - */ - public static Field getField(Class clz, String name) throws NoSuchFieldException { - var field = clz.getDeclaredField(name); - try { - field.setAccessible(true); - } catch (Throwable e) { - // Intentionally left empty - } - return field; - } - - /** - * Try to rethrow an exception or create a runtime exception and throw it. - */ - public static RuntimeException rethrow(Throwable e) { - if (e instanceof RuntimeException re) return re; - if (e instanceof Error er) throw er; - return new RuntimeException(e); - } - - /** - * Copy of Node.WVISIST - * Used to clear as exceptions might happen in the walk and left this bitset not cleared - */ - private static final BitSet NodeWalkVisit; - private static final java.util.Random WorkRandom; - - /** - * Write access to Iterate.MID_ASSERT - * Exception could happen mid assert and left this at true. Used to reset to false prior to parsing. - */ - private static final MethodHandle set_MID_ASSERT; - - /** - * Some problems might trigger debug output. Suppress this output with a null print stream during fuzzing. - */ - public static final PrintStream NULL_PRINT_STREAM = new PrintStream(OutputStream.nullOutputStream()); - - static { - try { - NodeWalkVisit = getFieldValue(Node.class, "WVISIT"); - WorkRandom = getFieldValue(getFieldValue(IterPeeps.class, "WORK"), "_R"); - set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT")); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * Check that the messages are similar. - */ - private static boolean similarMessages(String a, String b) { - if (a == null || b == null) return true; - a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); - b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); - return a.equals(b); - } - - /** - * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization - * and allow the other to be any stack trace. - */ - private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) { - if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing. - return a[0].equals(b[0]); - } - - /** - * Checks if two exceptions are caused by the same problem. - */ - public static boolean isExceptionFromSameCause(Throwable a, Throwable b) { - if (a == null || b == null) return a == b; // Handle the null case - if (a.getClass() != b.getClass()) return false; // Different classes - if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard - var s1 = a.getStackTrace(); - var s2 = b.getStackTrace(); - if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins - if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes - var o1 = a.getSuppressed(); - var o2 = b.getSuppressed(); - if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression - for (int i=0; i {} - - /** - * Holder for the rewrite rules - */ - private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) { - Replace(Pattern p, Object... replacements) { - this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new)); - } - private static Replacer from(Object o) { - if (o instanceof Replacer r) return r; - if (o instanceof Integer i) return (s, m)->s.append(m.group(i)); - if (o instanceof String r) return (s, m)->s.append(r); - throw new IllegalArgumentException(); - } - } - - /** - * List of rewrite rules. - */ - private static final Replace[] REPLACEMENTS = { - new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"), - new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""), - new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), - new Replace(Pattern.compile("\\b--"), "+"), - new Replace(Pattern.compile("-"), ""), - new Replace(Pattern.compile("\\b\\+-"), "-"), - new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), - new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1), - new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2), - new Replace(Pattern.compile("\\(([^()]+)\\)"), 1), - new Replace(Pattern.compile("\\d+\\+"), ""), - new Replace(Pattern.compile("\\+\\d+"), ""), - new Replace(Pattern.compile("\\n\\n+"), "\n"), - new Replace(Pattern.compile("^\\n+|\\n+$"), ""), - new Replace(Pattern.compile("else *"), ""), - new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "") - }; - - /** - * Predicate to test if the reduced script generates the same error. - */ - private final Predicate tester; - - /** - * Create a reducer with a test function. - * @param tester Test function which returns true when the same issue is found in the script. - */ - private Reducer(Predicate tester) { - this.tester = tester; - } - - /** - * Rewrites the script with one rewrite rule. - */ - private String doReplacement(String script, Pattern pattern, Replacer[] replacements) { - var matcher = pattern.matcher(script); - var sb = new StringBuilder(); - var last = 0; - var skip = 0; - outer: - while (matcher.find(last + skip)) { - sb.append(script, last, matcher.start()); - last = matcher.end(); - skip = last == matcher.start() ? 1 : 0; - var pos = sb.length(); - for (var replacement : replacements) { - replacement.accept(sb, matcher); - var tail = sb.length(); - sb.append(script, last, script.length()); - if (tester.test(sb.toString())) { - sb.setLength(tail); - continue outer; - } - sb.setLength(pos); - } - last = matcher.start(); - skip = 1; - } - sb.append(script, last, script.length()); - return sb.toString(); - } - - /** - * Clean up variable names from the random ones generated by the fuzzer. - */ - private String cleanVariables(String script) { - var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) ([0-9a-zA-Z_]+)").matcher(script); - int[] num = {0, 0}; - while (matcher.find()) { - var type = matcher.group(1); - var var = matcher.group(2); - if (type.equals("new")) continue; - String prefix = "v"; - int t = 0; - if (type.equals("struct")) { - t = 1; - prefix = "s"; - } - if (var.matches(prefix+"\\d+")) continue; - while (script.contains(prefix+num[t])) num[t]++; - var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]); - if (tester.test(n)) { - script = n; - } - } - return script; - } - - /** - * Run all rewrite rules once. - */ - private String doAllReplacements(String script) { - for (var replace: REPLACEMENTS) { - String old; - do { - old = script; - script = doReplacement(script, replace.pattern, replace.replacements); - } while (replace.iterate && !old.equals(script)); - } - return script; - } - - /** - * Reduce the script to a minimal version which produces the same problem with a test function. - * @param tester Test function which returns true when the same issue is found in the script. - */ - public static String reduce(String script, Predicate tester) { - var reducer = new Reducer(tester); - String old; - do { - old = script; - script = reducer.doAllReplacements(script); - script = reducer.cleanVariables(script); - } while (!script.equals(old)); - return script; - } - - /** - * Reduce the script to a minimal version which produces the same problem with a reproducer. - * @param ex The exception originally caused by the script - * @param reproducer Test function which throws an exception which will be checked against ex. - */ - public static String reduce(String script, Throwable ex, Consumer reproducer) { - return reduce(script, s->{ - Throwable nex = null; - try { - reproducer.accept(s); - } catch (Throwable e) { - nex = e; - } - return FuzzerUtils.isExceptionFromSameCause(nex, ex); - }); - } - -} diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java deleted file mode 100644 index 155063ab..00000000 --- a/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ /dev/null @@ -1,792 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.ScopeNode; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Random; -import java.util.function.Predicate; - -/** - * Generate a pseudo random script. - * This generator will generate the same script for two random number generators initialized with the same seed. - * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions. - * It is guaranteed to terminate. - */ -public class ScriptGenerator { - - private static final int MAX_STATEMENTS_PER_BLOCK = 5; - private static final int MAX_BINARY_EXPRESSIONS_PER_EXPRESSION = 10; - private static final int MAX_UNARY_EXPRESSIONS_PER_EXPRESSION = 6; - private static final int MAX_NAME_LENGTH = 10; - private static final int MAX_BLOCK_DEPTH = 4; - private static final int MAX_EXPRESSION_DEPTH = 10; - - /** - * Number of spaces per indentation - */ - private static final int INDENTATION = 4; - /** - * Valid characters for identifiers. The last 10 are not valid for the first character. - */ - private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; - /** - * List of keywords not valid for identifiers. - */ - private static final HashSet KEYWORDS; - - static { - try { - KEYWORDS = FuzzerUtils.getFieldValue(new Parser(""), "KEYWORDS"); - } catch (Throwable e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * Flag for a statement that it does not pass on control flow to the next statement. - */ - private static final int FLAG_STOP = 0x01; - /** - * Flag for a statement that it contains a final if statement without an else branch. - */ - private static final int FLAG_IF_WITHOUT_ELSE = 0x02; - /** - * Binary operators. - */ - private static final String[] BINARY_OP = {"==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"}; - /** - * Unary operators. - */ - private static final String[] UNARY_OP = {"-"}; - - private static class Type { - final String name; - Type(String name) {this.name=name;} - boolean isa(Type other) { return this == other; } - } - - private static class TypeStruct extends Type { - record Field (String name, Type type) {} - final Field[] fields; - final TypeNullable nullable = new TypeNullable(this); - TypeStruct(String name, Field[] fields) { - super(name); - this.fields = fields; - } - boolean isa(Type other) { return this == other || other == nullable; } - } - - private static class TypeNullable extends Type { - - final Type base; - - TypeNullable(Type base) { - super(base.name+"?"); - this.base = base; - } - - } - - private static final Type TYPE_INT = new Type("int"); - - - private static class Variable { - final String name; - final Type declared; - final Variable shadowing; - Type type; - boolean shadowed = false; - - Variable(String name, Type type, Variable var) { - this.name = name; - this.declared = type; - this.type = type; - this.shadowing = var; - } - } - - /** - * The random number generator used for random decisions. - */ - private final Random random; - /** - * Indentation depth. - */ - private int indentation = 0; - /** - * Output string builder - */ - private final StringBuilder sb; - /** - * Number of nested loops - */ - private int loopDepth = 0; - /** - * Current scope start in the variables list. - */ - private int currScopeStart = 0; - /** - * The depth of blocks allowed. - */ - private int depth = MAX_BLOCK_DEPTH; - /** - * The depth of expressions allowed. - */ - private int exprDepth = MAX_EXPRESSION_DEPTH; - /** - * Current variables in scope. - */ - private final ArrayList variables = new ArrayList<>(); - /** - * All defined structs. - */ - private final ArrayList structs = new ArrayList<>(); - /** - * Allow to declare structs. - */ - private boolean allowStructs = true; - /** - * If this needs to generate valid scripts or is also allowed to generate invalid ones. - */ - private boolean generateValid; - /** - * If this script might be invalid. - */ - private boolean maybeInvalid = false; - - - /** - * - * @param random the random number generator. - * @param sb the script will be written to this string builder. - * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs. - */ - public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) { - this.random = random; - this.sb = sb; - this.generateValid = generateValid; - } - - /** - * Random number with log distribution - * @param max The maximum value - * @return A random number in the range [0, max) with higher numbers being more uncommon. - */ - private int randLog(int max) { - max--; - int n = 1<=0; i--) { - if ((num & (1<2) return false; - maybeInvalid = true; - return true; - } - - /** - * Lookup a variable by name - * @param name name of the variable - * @return The index of the variable in variables or -1 - */ - private int lookup(String name) { - for (int i=variables.size()-1; i>=0; i--) { - if (variables.get(i).name.equals(name)) return i; - } - return -1; - } - - /** - * Lookup a variable by name - * @param name name of the variable - * @return THe variable of null if not found - */ - private Variable lookupVar(String name) { - int idx = lookup(name); - return idx == -1 ? null : variables.get(idx); - } - - /** - * Add a variable to the current scope - * @param name The name of the variable - * @param type The declared type of the variable - */ - private void addVariable(String name, Type type) { - var old = lookupVar(name); - variables.add(new Variable(name, type, old)); - if (old != null) old.shadowed = true; - } - - /** - * Pop variables to level to - * @param to Level until which variables should be popped - */ - private void popVariables(int to) { - while (variables.size() > to) { - var v = variables.removeLast(); - if (v.shadowing != null) v.shadowing.shadowed = false; - } - } - - /** - * Get a random visible variable patching pred - * @param pred The predicate the variable needs to match - * @return A random visible variable matching pred or null if no variable mached pred. - */ - private Variable findVisibleVariablesMatching(Predicate pred) { - int num=0; - for (var v:variables) { - if (!v.shadowed && pred.test(v)) num++; - } - if (num == 0) return null; - num = random.nextInt(num); - for (var v:variables) { - if (!v.shadowed && pred.test(v) && num-- == 0) return v; - } - throw new AssertionError(); - } - - /** - * Get a structure by name - * @param name Name of the structure - * @return The structure of null - */ - private TypeStruct getStruct(String name) { - for (var s:structs) { - if (s.name.equals(name)) return s; - } - return null; - } - - /** - * Generate a variable name. This might be new random name or a name from an outer scope. - * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope. - * @return The variable name. - */ - private String getVarName() { - if (currScopeStart > 0 && random.nextInt(10) > 7) { - // Use a variable outside the current scope. - var v = variables.get(random.nextInt(currScopeStart)); - if (lookup(v.name) < currScopeStart) return v.name; - if (!generateValid) { - maybeInvalid = true; - return v.name; - } - } - // Generate a new random variable name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - /** - * Get a random structure name - * @return A random structure name - */ - private String getStructName() { - if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name; - // Generate a new random struct name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || getStruct(v) != null) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - /** - * Get a random type - * @return A random type - */ - private Type getType() { - if (structs.isEmpty() || random.nextBoolean()) return TYPE_INT; - var idx = random.nextInt(structs.size()); - TypeStruct struct = structs.get(idx); - if (random.nextBoolean()) return struct.nullable; - return struct; - } - - /** - * Generates a program and writes it into the string builder supplied in the constructor. - * @return If the program is allowed to be invalid returns if something potentially invalid was generated. - */ - public boolean genProgram() { - var oldGenerateValid = generateValid; - if (!generateValid && random.nextBoolean()) generateValid = true; - this.maybeInvalid = false; - addVariable(ScopeNode.ARG0, TYPE_INT); - currScopeStart = variables.size(); - if ((genStatements() & FLAG_STOP) == 0) { - if (generateValid || random.nextInt(10)<7) { - genReturn(); - } else { - this.maybeInvalid = true; - } - } - generateValid = oldGenerateValid; - return !this.maybeInvalid; - } - - /** - * Generate a list of statements. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated - */ - public int genStatements() { - var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1)); - for (int i=0; i allowStructs || generateInvalid() ? genStruct() : genAssignment(); - case 1 -> genBlock(); - case 2 -> genIf(); - case 3 -> genCountedLoop(); - case 4 -> genWhile(); - case 5, 6 -> genDecl(); - case 7, 8, 9 -> genAssignment(); - case 10 -> genNullCheck(); - default -> genExit(); - }; - } - - /** - * Generate a new structure - * @return 0 - */ - public int genStruct() { - var name = getStructName(); - var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)]; - sb.append("struct ").append(name).append(" {\n"); - indentation += INDENTATION; - for (int i=0; i { - depth++; - indentation -= INDENTATION; - var ret = genBlock(); - indentation += INDENTATION; - depth--; - yield ret; - } - case 6 -> genIf(); - case 7 -> genCountedLoop(); - case 8 -> genWhile(); - case 9 -> genAssignment(); - case 10 -> genNullCheck(); - default -> genExit(); - }; - depth++; - } - indentation -= INDENTATION; - allowStructs = oldAllowStructs; - return res; - } - - /** - * Generate an exit. This can be return or break and continue when in a loop. - * @return FLAG_STOP - */ - public int genExit() { - if (loopDepth == 0 && generateValid) return genReturn(); - return switch (random.nextInt(7)) { - case 0,1,2 -> { - if (loopDepth == 0) maybeInvalid = true; - sb.append("continue;"); - yield FLAG_STOP; - } - case 3,4,5 -> { - if (loopDepth == 0) maybeInvalid = true; - sb.append("break;"); - yield FLAG_STOP; - } - default -> genReturn(); - }; - } - - /** - * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator. - * @return flag FLAG_STOP for the generated statement - */ - public int genBlock() { - if (depth == 0) { - sb.append("{}"); - return 0; - } - var oldAllowStructs = allowStructs; - var oldCSS = currScopeStart; - allowStructs = false; - currScopeStart = variables.size(); - depth--; - sb.append("{\n"); - indentation += INDENTATION; - var stop = genStatements(); - indentation -= INDENTATION; - printIndentation().append("}"); - popVariables(currScopeStart); - depth++; - currScopeStart = oldCSS; - allowStructs = oldAllowStructs; - return stop & ~FLAG_IF_WITHOUT_ELSE; - } - - /** - * Generate an if statement with an optional else block. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement - */ - public int genIf() { - sb.append("if("); - genExpression(TYPE_INT); - sb.append(") "); - var stop = genStatementBlock(); - if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { - sb.append("\n"); - printIndentation().append("else "); - stop &= genStatementBlock(); - } else { - stop = FLAG_IF_WITHOUT_ELSE; - } - return stop; - } - - /** - * Generate a block with a null check - * if (nullable) { handle_nullable_as_non_nullable; } - */ - public int genNullCheck() { - var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable); - if (v == null) return genIf(); - var n = (TypeNullable)v.declared; - boolean negate = random.nextBoolean(); - sb.append("if("); - if (negate) sb.append("!"); - sb.append(v.name).append(")"); - if (!negate) v.type = n.base; - var stop = genStatementBlock(); - v.type = n; - if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { - sb.append("\n"); - printIndentation().append("else "); - if (negate) v.type = n.base; - stop &= genStatementBlock(); - v.type = n; - } else { - stop = FLAG_IF_WITHOUT_ELSE; - } - return stop; - } - - /** - * Generate a while loop. - * @return 0 - */ - public int genWhile() { - sb.append("while("); - genExpression(TYPE_INT); - sb.append(") "); - loopDepth++; - genStatementBlock(); - loopDepth--; - return 0; - } - - /** - * Generate a counted loop of the form - * - * { - * int varname = start_expr; - * while (varname < end_expr) loop_body; - * } - * - * @return 0 - */ - public int genCountedLoop() { - if (depth == 0) return genWhile(); - var oldCSS = currScopeStart; - currScopeStart = variables.size(); - sb.append("{\n"); - indentation += INDENTATION; - String name = getVarName(); - printIndentation().append("int ").append(name).append("="); - genExpression(TYPE_INT); - sb.append(";\n"); - addVariable(name, TYPE_INT); - printIndentation().append("while(").append(name).append("<"); - genExpression(TYPE_INT); - sb.append(") {\n"); - indentation += INDENTATION; - depth--; - loopDepth++; - printIndentation().append(name).append("=").append(name).append("+"); - genExpression(TYPE_INT); - sb.append(";\n"); - genStatements(); - loopDepth--; - depth++; - indentation -= INDENTATION; - printIndentation().append("}\n"); - indentation -= INDENTATION; - printIndentation().append("}"); - popVariables(currScopeStart); - currScopeStart = oldCSS; - return 0; - } - - /** - * Generate a declaration statement. - * @return 0 - */ - public int genDecl() { - var type = getType(); - var name = getVarName(); - sb.append(generateInvalid() ? getRandomName() : type.name).append(" ").append(name); - if (!(type instanceof TypeNullable) || random.nextBoolean()) { - sb.append("="); - genExpression(type); - } - sb.append(";"); - addVariable(name, type); - return 0; - } - - /** - * Generate an assignment statement. - * @return 0 - */ - public int genAssignment() { - Type type; - Type declared; - if (generateInvalid()) { - sb.append(getRandomName()); - type = getType(); - declared = type; - } else { - var v = findVisibleVariablesMatching(va->true); - if (v == null) return genDecl(); - sb.append(v.name); - type = v.type; - declared = v.declared; - } - if (generateInvalid()) { - var name = getRandomName(); - sb.append(".").append(name); - declared = getType(); - } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) { - var field = s.fields[random.nextInt(s.fields.length)]; - sb.append(".").append(field.name); - declared = field.type; - } - sb.append("="); - genExpression(declared); - sb.append(";"); - return 0; - } - - /** - * Generate a return statement. - * @return FLAG_STOP - */ - public int genReturn() { - var type = getType(); - sb.append("return "); - genExpression(type); - sb.append(";"); - return FLAG_STOP; - } - - /** - * Generate a binary expression. - * This method does not care about operator precedence. - */ - public void genExpression(Type type) { - if (generateInvalid()) type = getType(); - if (type != TYPE_INT) { - genUnary(type); - return; - } - var num = randLog(MAX_BINARY_EXPRESSIONS_PER_EXPRESSION); - while(num-->0) { - genUnary(TYPE_INT); - sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); - } - genUnary(TYPE_INT); - } - - /** - * Generate a unary expression. - */ - public void genUnary(Type type) { - if (generateInvalid()) type = getType(); - if (type != TYPE_INT) { - genSuffix(type); - return; - } - var num = randLog(MAX_UNARY_EXPRESSIONS_PER_EXPRESSION); - while(num-->0) { - sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); - } - genSuffix(TYPE_INT); - } - - /** - * Generate - */ - public void genSuffix(Type type) { - if (generateInvalid()) type = getType(); - if (type == TYPE_INT && random.nextBoolean()) { - var t = getType(); - if (generateInvalid()) { - var field = getRandomName(); - genPrimary(t); - sb.append(".").append(field); - return; - } else if (t instanceof TypeStruct s && s.fields.length > 0) { - var field = s.fields[random.nextInt(s.fields.length)]; - if (field.type == TYPE_INT) { - genPrimary(t); - sb.append(".").append(field.name); - return; - } - } - } - genPrimary(type); - } - - /** - * Generate a primary expression. - */ - public void genPrimary(Type type) { - if (generateInvalid()) type = getType(); - var rand = random.nextInt(10); - if (rand == 0 && exprDepth != 0) { - sb.append("("); - exprDepth--; - genExpression(type); - exprDepth++; - sb.append(")"); - } else if (rand < 6) { - genVariable(type); - } else { - genConst(type); - } - } - - /** - * Generate a constant. - */ - public void genConst(Type type) { - if (generateInvalid()) type = getType(); - if (type == TYPE_INT) { - var rand = random.nextInt(10); - switch (rand) { - case 0 -> sb.append("true"); - case 1 -> sb.append("false"); - default -> sb.append(random.nextInt(1<<(rand-2))); - } - } else if (type instanceof TypeNullable n) { - if (random.nextBoolean()) { - sb.append("null"); - } else { - sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name); - } - } else { - sb.append("new ").append(generateInvalid() ? getRandomName() : type.name); - } - } - - /** - * Generate a variable. If there are none generate a number instead. - * If the program is allowed to be invalid a random name can be generated in some cases. - */ - public void genVariable(Type type) { - if (generateInvalid()) { - sb.append(getRandomName()); - return; - } - if (generateInvalid()) type = getType(); - var t = type; - var v = findVisibleVariablesMatching(va->va.type.isa(t)); - if (v == null) genConst(type); - else sb.append(v.name); - } - -} diff --git a/chapter10b/Makefile b/chapter10b/Makefile deleted file mode 100644 index 5ed21084..00000000 --- a/chapter10b/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# Standalone compiler snapshot; shared rules also work in linearized checkouts. -include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) -test_cp += com.seaofnodes.simple.FuzzerWrap - -.PHONY: fuzzer -fuzzer: $(test_classes) - java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter10b/README.md b/chapter10b/README.md deleted file mode 100644 index 7cb4cc75..00000000 --- a/chapter10b/README.md +++ /dev/null @@ -1,218 +0,0 @@ -# Chapter 10b: Splitting Memory - -[Previous: Chapter 10a](../chapter10a/README.md) | -[Next: Chapter 11](../chapter11/README.md) - -Chapter 10a made memory effects explicit with one SSA memory value. This -chapter keeps exactly that parser model and recovers independent memory chains -through graph rewrites. There is no new language syntax; the -[grammar](docs/10-grammar.md), pointer types, and null checks are unchanged. - -## Why split memory? - -```java -struct Point { int x; int y; } -Point p = new Point; -p.x = 42; -while (arg > 0) { - p.y = p.y + arg; - arg = arg - 1; -} -return p.x; -``` - -The loop changes `y`, so it cannot affect `x`. With one memory chain the load -of `x` follows the loop's stores. Splitting the fields lets ordinary -load-after-store forwarding turn the return value into 42. - -![Independent field dependencies](docs/example1.svg) - -The graph shows the split chains before the final Load folds. Its memory input -goes straight to the Store of 42, bypassing the loop's `y` chain. As in 10a, -definitions appear above uses, apart from loop backedges, and arrows point -from use to definition. As in 10a, small, detached True/False projections -show the source branches gating Loads and Stores. Other control-flow context, -including Regions and their Phi bindings, is omitted. Chapter 11 chooses -the actual scheduling inputs; the vertical placement of independent chains -does not specify their execution order. - -## Equivalence-class aliasing - -Each struct field has an integer alias identity. Two different fields receive -different identities, even if they have the same spelling in different structs. -Two instances of the same struct share their field aliases. Thus different -classes never alias; accesses in the same class **may alias**. Same-class does -not mean same-address: forwarding still checks pointer identity. - -The parser assigns field identities at struct declarations, beginning at 2. -These identities are independent of Start's output indices. The default slot -of a memory aggregate is 1; precise slots are greater than 1. - -We do not allocate a Start projection, scope binding, or loop Phi for every -declared field. Start supplies one memory value, Scope holds one `$mem` binding, -and Return consumes `{ctrl, $mem, result}`. As with Start's outputs, control is -in slot 0 and memory in slot 1. The optimizer introduces slices -only where graph operations require them. - -## Three memory nodes - -| Node | Meaning | -|---|---| -| `MemMerge` | A complete memory state: a default plus precise alias overrides | -| `MemPhi` | One alias selected according to the incoming control path | -| `BulkMemPhi` | All aliases except an explicit exclusion set, selected by control | - -`MemMerge` combines independent pieces of memory at one program point. It is -not a control-flow merge. For example, after storing to alias `x`: - -```text -prior = current memory -store = Store_x(prior, pointer, value) -current memory = MemMerge(default=prior, x=store) -``` - -The Store selects its precise input from any incoming MemMerge. Loads and -precise memory Phis do the same. A missing precise entry falls back to the -default, provided that default still covers the requested alias. - -The explicit entries override the default: the semantic slices are disjoint, -and their union covers all memory. Flattening stacked aggregates preserves -entries that the outer aggregate has not replaced. This allows an `x` store -and a later `y` store to remain visible together at Return. - -The graph below shows `p.x = arg; p.y = 42; return p;` after redundant -initialization stores have been removed. MemMerge takes the default memory -in slot 1 and the `x` and `y` overrides in slots 2 and 3. - -![An aggregate preserves both updates](docs/example2a.svg) - - -## Splitting a memory Phi - -Scope initially creates a BulkMemPhi wherever the ordinary SSA algorithm needs -to merge memory. It starts by covering all aliases and an empty exclusion set -while the merge point is finalized. Later, peeps will split out precise memory -slices from the bulk memory, lazily on demand. - -Precise single aliases can be split out from the bulk by this transformation: - -```text -BulkPhi(R, left, right) - becomes -MemMerge( - default = BulkPhi_except{x}(R, left, right), - x = MemPhi_x(R, slice(left,x), slice(right,x))) -``` - -The new precise Phi and the remaining bulk Phi share a Region (omitted from -the illustrations) and matching control-path indices. A loop uses the same -transformation, with the backedge among those inputs. Each further split preserves all previously extracted -slices (and peephole optimizations tidy up the stacked splits). - -Here the two branches store 3 or 4 into `p.x`, then return `p`. The graph shows -the partition immediately after extracting `x`, before further simplification -of the bulk Phi and its incoming aggregates. - -![Precise and bulk Phis share a control merge](docs/example2b.svg) - -This transform is invasive, and is disallowed from being called recursively; -you can end up splitting around a loop (which is good) but then visiting a -1/2-split loop head (which is bad). The new nodes from the split are put -on the worklist and visited after the current split completes. - -Since bulk Phis merge a bunch of aliases, they have to take care when -peepholing vs MemMerges: there cannot be any overlap between the MemMerge -precise merges and the bulk Phi. - -Phi refactoring can combine matching Loads or Stores as in Chapter 10a (pushing -up or pulling down). The Phi new memory operands can use precise MemPhis for -the alias. This peep requires precise aliases, and is otherwise blocked by -BulkMems - and thus is a trigger for splitting a BulkMem. - -This also extends the worklist lesson from Chapter 9. A bulk Phi inspects its -users, so changes to a user's partition must wake the producer - job for -`addDep()`. Selecting a slice from an aggregate can expose a new bulk-Phi -user; that selected definition must be queued too. In short, any time we're -working with MemMerge, MemPhi or BulkMemPhi we can expect some non-local -dependencies. - -Memory splitting can increase node count - but Phis (Mem, Bulk or otherwise) -make no code. Progress comes from extracting an alias from a bulk Phi and -removing some Loads & Stores, rather than from requiring every rewrite -to shrink the graph. In other words, the total Load/Store count is going down -even if the MemPhi count is going up. - - - -## Types - -The type lattice retains Chapter 10a's six domains, colors, and notation: -`⊤` means top, `⊥` means bottom, and a suffix names the domain. The other five -domains are unchanged; memory now adds a flat element `MEM#N` for each precise -alias between memory top (`TypeMem.TOP`) and memory bottom (`TypeMem.BOT`). - -![The type domains, including precise memory aliases](docs/lattice.svg) - -The parallel `MEM#2`, `MEM#3`, and further alias elements describe independent -field slices. For the first `Point` declaration above, these two aliases identify -`Point.x` and `Point.y`. Meeting different aliases gives memory bottom; joining -them gives memory top. Each precise alias is its own dual. (And the ellipsis stands -for more parallel aliases, not a type representing a set of aliases). - -Memory bottom covers all memory. The default aggregate slot numbered 1 is a -graph input position, not a precise field alias in this diagram. Types do not -track field values, private objects, or escape information. Bulk exclusion sets -and the default-plus-overrides structure of MemMerge belong to graph nodes, -not to an expanded type lattice. As in Chapter 10a, diagram edges show lattice -order, with top above bottom; field and tuple-element type lattices are elided. - - -## Evaluation and ordering - -MemMerge is a dependency node; it emits no heap operation. Its evaluator result -is a memory token, while Store mutates the object's fields. The scheduler must -distinguish aggregation from overwriting memory. A MemMerge is not a clobber, -and stores in different alias classes do not impose load/store anti-dependencies. - -In `int old = p.x; p.x = arg; p.y = arg; return old;`, the Load must precede -the later `x` Store; the `y` Store has an independent memory chain. The graph -retains the Load to show this scheduling requirement before local folding. - -![Ordering applies within the affected alias](docs/example2c.svg) - -Tests cover independent-field folding, parallel memory Phis, multiple field -updates surviving at Return, early returns, nested loops with break/continue, -and reads before subsequent stores through potentially aliased pointers. - -## What remains for later chapters? - -Chapter 10b knows every access's alias while parsing. What is lazy here is the -construction of memory partitions. Chapter 25 also allows an existing access's -alias to become known later, using the same representation. Its private -constructor memory, escape tracking, incomplete types, and module alias -remapping are separate extensions and are not needed here. - -Chapters 11-24 use this representation, including alias-specific load/store -anti-dependencies. Chapter 15 adds allocation with partial memory and typed -alias contents. Chapter 16 extends initialization to constructor values, and -Chapter 18 carries whole memory across function calls. - -## Build and test - -With JDK 21 or newer, from this directory: - -```sh -make lib -make tests -make release -make view -``` - -Make builds this complete snapshot without Maven. `make release` writes -`build/release/simple.jar`; `make view` launches the shared graph viewer. -Both the root Maven reactor and the supplied IDEA module descriptor include -the chapter. From the repository root, test both halves with: - -```sh -make tests CHAPTERS="chapter10a chapter10b" -``` diff --git a/chapter10b/chapter10b.iml b/chapter10b/chapter10b.iml deleted file mode 100644 index 74da25d0..00000000 --- a/chapter10b/chapter10b.iml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/chapter10b/docs/10-grammar.md b/chapter10b/docs/10-grammar.md deleted file mode 100644 index 9a80d6ea..00000000 --- a/chapter10b/docs/10-grammar.md +++ /dev/null @@ -1,125 +0,0 @@ -# Grammar for Chapter 10b - -```antlrv4 -grammar SimpleLanguage; - -program - : statement+ EOF - ; - -statement - : returnStatement - | structDeclaration - | declStatement - | blockStatment - | expressionStatement - | ifStatement - | whileStatement - | breakStatement - | continueStatment - ; - -field - : 'int' IDENTIFIER ';' - ; - -fields - : field+ - ; - -structDeclaration - : 'struct' IDENTIFIER '{' fields '}' - ; - -whileStatement - : 'while' '(' expression ')' statement - ; - -breakStatement - : 'break' ';' - ; - -continueStatement - : 'continue' ';' - ; - -ifStatement - : 'if' '(' expression ')' statement ('else' statement)? - ; - - -expressionStatement - : IDENTIFIER '=' expression ';' - | fieldExpression '=' expression ';' - ; - -blockStatement - : '{' statement+ '}' - ; - -structName - : IDENTIFIER - ; - -declStatement - : 'int' IDENTIFIER '=' expression ';' - | structName ('?')? IDENTIFIER '=' expression ';' - ; - -returnStatement - : 'return' expression ';' - ; - -expression - : comparisonExpression - ; - -comparisonExpression - : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* - ; - -additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* - ; - -multiplicativeExpression - : unaryExpression (('*' | '/') unaryExpression)* - ; - -unaryExpression - : ('-') unaryExpression - | '!' unaryExpression - | primaryExpression - ; - -newExpression - : 'new' IDENTIFIER - ; - -fieldExpression - : primaryExpresson '.' IDENTIFIER - ; - -primaryExpression - : IDENTIFIER - | INTEGER_LITERAL - | 'true' - | 'false' - | 'null' - | newExpression - | '(' expression ')' - | fieldExpression - ; - -INTEGER_LITERAL - : [1-9][0-9]* - | [0] - ; - -IDENTIFIER - : NON_DIGIT (NON_DIGIT | DIGIT)* - ; - -NON_DIGIT: [a-zA-Z_]; -DEC_DIGIT: [0-9]; -``` diff --git a/chapter10b/docs/Makefile b/chapter10b/docs/Makefile deleted file mode 100644 index 6fa0f057..00000000 --- a/chapter10b/docs/Makefile +++ /dev/null @@ -1 +0,0 @@ -include ../../graph/docs.mk diff --git a/chapter10b/docs/example1.gv b/chapter10b/docs/example1.gv deleted file mode 100644 index 5e059d77..00000000 --- a/chapter10b/docs/example1.gv +++ /dev/null @@ -1,20 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=BT; - - M [label="Initial memory" shape=octagon style="filled" fillcolor="#dcecff"]; - X [label="Store x = 42" shape=octagon style="filled" fillcolor="#dcecff"]; - Y [label="Loop: Store y" shape=octagon style="filled" fillcolor="#dcecff"]; - L [label="Load x (42)" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - L -> X -> M [color="#2867b2"]; Y -> M [color="#2867b2"]; - MemMerge -> X [color="#2867b2"]; MemMerge -> Y [color="#2867b2"]; - Return -> MemMerge [color="#2867b2"]; Return -> L; - { rank=same; MemMerge -> L [style=invis]; } - Y -> X [style=invis]; // Layout only: keep the stores in code order. - Return [shape=box style="filled" fillcolor="#fff1c4"]; - MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; -} diff --git a/chapter10b/docs/example1.json b/chapter10b/docs/example1.json deleted file mode 100644 index 67c277e1..00000000 --- a/chapter10b/docs/example1.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10b/docs/example1.svg", - "step": 1, - "roots": [24], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 7, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 11, "label": "MemPhi y", "type": "MEM#3", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 19, "role": "MEM", "label": null}], "proj": null}, - {"id": 12, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 15, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 16, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, - {"id": 17, "label": "Load p.y", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 18, "label": "+", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "DATA", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null}, - {"id": 19, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 18, "role": "DATA", "label": null}], "proj": null}, - {"id": 20, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 21, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "DATA", "label": null}, {"idx": 2, "def": 20, "role": "DATA", "label": null}], "proj": null}, - {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 8, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null}, - {"id": 23, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 16, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 24, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 23, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10b/docs/example1.svg b/chapter10b/docs/example1.svg deleted file mode 100644 index 46faf818..00000000 --- a/chapter10b/docs/example1.svg +++ /dev/null @@ -1,155 +0,0 @@ -#6[0] → #1 · ASSOC#7[0] → #1 · ASSOC#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #7 · DATA#9[1] → #3 · MEM#9[2] → #5 · DATA#9[3] → #6 · DATA#11[1] → #9 · MEM#11[2] → #19 · MEM#12[1] → #4 · DATA#12[2] → #21 · DATA#17[0] → #15 · CTRL#17[1] → #11 · MEM#17[2] → #5 · DATA#18[1] → #17 · DATA#18[2] → #12 · DATA#19[0] → #15 · CTRL#19[1] → #11 · MEM#19[2] → #5 · DATA#19[3] → #18 · DATA#20[0] → #1 · ASSOC#21[1] → #12 · DATA#21[2] → #20 · DATA#22[1] → #3 · MEM#22[2] → #8 · MEM#22[3] → #11 · MEM#23[0] → #16 · CTRL#23[1] → #8 · MEM#23[2] → #5 · DATA#24[1] → #22 · MEM#24[2] → #23 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0#42  #7#7 #42 -Type not computed - -[0] → #1 ASSOC0Store p.x  #8MEM#2#8 Store p.x -MEM#2 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #7 DATA0123Store p.y  #9MEM#3#9 Store p.y -MEM#3 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123MemPhi y  #11MEM#3#11 MemPhi y -MEM#3 - -[0] → — ASSOC -[1] → #9 MEM -[2] → #19 MEM012Phi arg  #12#12 Phi arg -Type not computed - -[0] → — ASSOC -[1] → #4 DATA -[2] → #21 DATA012True  #15/0#15 True -Projection 0 of #0 -Type not computed - -False  #16/1#16 False -Projection 1 of #0 -Type not computed - -Load p.y  #17#17 Load p.y -Type not computed - -[0] → #15 CTRL -[1] → #11 MEM -[2] → #5 DATA -[3] → — DATA0123+  #18#18 + -Type not computed - -[0] → — CTRL -[1] → #17 DATA -[2] → #12 DATA012Store p.y  #19MEM#3#19 Store p.y -MEM#3 - -[0] → #15 CTRL -[1] → #11 MEM -[2] → #5 DATA -[3] → #18 DATA0123#1  #20#20 #1 -Type not computed - -[0] → #1 ASSOC0-  #21#21 - -Type not computed - -[0] → — CTRL -[1] → #12 DATA -[2] → #20 DATA012MemMerge  #22#22 MemMerge -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #8 MEM -[3] → #11 MEM0123Load p.x  #23#23 Load p.x -Type not computed - -[0] → #16 CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → — DATA0123Return  #24#24 Return -Type not computed - -[0] → — CTRL -[1] → #22 MEM -[2] → #23 DATA012 diff --git a/chapter10b/docs/example2a.gv b/chapter10b/docs/example2a.gv deleted file mode 100644 index 187e63b9..00000000 --- a/chapter10b/docs/example2a.gv +++ /dev/null @@ -1,17 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - M [label="Default memory" shape=octagon style="filled" fillcolor="#dcecff"]; - X [label="Store x" shape=octagon style="filled" fillcolor="#dcecff"]; - Y [label="Store y" shape=octagon style="filled" fillcolor="#dcecff"]; - X -> M [color="#2867b2"]; Y -> M [color="#2867b2"]; - MemMerge -> M [xlabel="default",color="#2867b2"]; MemMerge -> X [xlabel="x",color="#2867b2"]; - MemMerge -> Y [xlabel="y",color="#2867b2"]; Return -> MemMerge [color="#2867b2"]; - Return [shape=box style="filled" fillcolor="#fff1c4"]; - MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; -} diff --git a/chapter10b/docs/example2a.json b/chapter10b/docs/example2a.json deleted file mode 100644 index 2a9461b6..00000000 --- a/chapter10b/docs/example2a.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10b/docs/example2a.svg", - "step": 1, - "roots": [11], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 9, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 8, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 9, "role": "MEM", "label": null}], "proj": null}, - {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10b/docs/example2a.svg b/chapter10b/docs/example2a.svg deleted file mode 100644 index b43a55b4..00000000 --- a/chapter10b/docs/example2a.svg +++ /dev/null @@ -1,103 +0,0 @@ -#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #4 · DATA#8[0] → #1 · ASSOC#9[1] → #3 · MEM#9[2] → #5 · DATA#9[3] → #8 · DATA#10[1] → #3 · MEM#10[2] → #7 · MEM#10[3] → #9 · MEM#11[1] → #10 · MEM#11[2] → #5 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0Store p.x  #7MEM#2#7 Store p.x -MEM#2 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #4 DATA0123#42  #8#8 #42 -Type not computed - -[0] → #1 ASSOC0Store p.y  #9MEM#3#9 Store p.y -MEM#3 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #8 DATA0123MemMerge  #10#10 MemMerge -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #7 MEM -[3] → #9 MEM0123Return  #11#11 Return -Type not computed - -[0] → — CTRL -[1] → #10 MEM -[2] → #5 DATA012 diff --git a/chapter10b/docs/example2b.gv b/chapter10b/docs/example2b.gv deleted file mode 100644 index 39b87a6a..00000000 --- a/chapter10b/docs/example2b.gv +++ /dev/null @@ -1,19 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - P [label="MemPhi(x)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; - B [label="BulkMemPhi(All except x)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; - P -> Left [xlabel="slice x",color="#2867b2"]; P -> Right [xlabel="slice x",color="#2867b2"]; - B -> Left [color="#2867b2"]; B -> Right [color="#2867b2"]; - P -> Region [style=dashed,color="#9396a3"]; B -> Region [style=dashed,color="#9396a3"]; - MemMerge -> P [xlabel="x",color="#2867b2"]; MemMerge -> B [xlabel="default",color="#2867b2"]; - MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; - Left [shape=octagon style="filled" fillcolor="#dcecff"]; - Right [shape=octagon style="filled" fillcolor="#dcecff"]; - Region [shape=trapezium style="filled" fillcolor="#fff1c4"]; -} diff --git a/chapter10b/docs/example2b.json b/chapter10b/docs/example2b.json deleted file mode 100644 index 3d919618..00000000 --- a/chapter10b/docs/example2b.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10b/docs/example2b.svg", - "step": 1, - "roots": [23], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 8, "role": "MEM", "label": null}], "proj": null}, - {"id": 11, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 12, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, - {"id": 13, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 14, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 11, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 13, "role": "DATA", "label": null}], "proj": null}, - {"id": 15, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 16, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 12, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 15, "role": "DATA", "label": null}], "proj": null}, - {"id": 17, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null}, - {"id": 18, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null}, - {"id": 20, "label": "MemPhi x", "type": "MEM#2", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null}, - {"id": 21, "label": "BulkMemPhi except x", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "MEM", "label": null}], "proj": null}, - {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 21, "role": "MEM", "label": null}, {"idx": 2, "def": 20, "role": "MEM", "label": null}], "proj": null}, - {"id": 23, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10b/docs/example2b.svg b/chapter10b/docs/example2b.svg deleted file mode 100644 index 54ddad9d..00000000 --- a/chapter10b/docs/example2b.svg +++ /dev/null @@ -1,150 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #3 · MEM#9[2] → #7 · MEM#9[3] → #8 · MEM#13[0] → #1 · ASSOC#14[0] → #11 · CTRL#14[1] → #7 · MEM#14[2] → #5 · DATA#14[3] → #13 · DATA#15[0] → #1 · ASSOC#16[0] → #12 · CTRL#16[1] → #7 · MEM#16[2] → #5 · DATA#16[3] → #15 · DATA#17[1] → #9 · MEM#17[2] → #14 · MEM#18[1] → #9 · MEM#18[2] → #16 · MEM#20[1] → #14 · MEM#20[2] → #16 · MEM#21[1] → #17 · MEM#21[2] → #18 · MEM#22[1] → #21 · MEM#22[2] → #20 · MEM#23[1] → #22 · MEM#23[2] → #5 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x -MEM#2 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y -MEM#3 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123MemMerge  #9#9 MemMerge -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #7 MEM -[3] → #8 MEM0123True  #11/0#11 True -Projection 0 of #0 -Type not computed - -False  #12/1#12 False -Projection 1 of #0 -Type not computed - -#3  #13#13 #3 -Type not computed - -[0] → #1 ASSOC0Store p.x  #14MEM#2#14 Store p.x -MEM#2 - -[0] → #11 CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #13 DATA0123#4  #15#15 #4 -Type not computed - -[0] → #1 ASSOC0Store p.x  #16MEM#2#16 Store p.x -MEM#2 - -[0] → #12 CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #15 DATA0123MemMerge  #17#17 MemMerge -Type not computed - -[0] → — CTRL -[1] → #9 MEM -[2] → #14 MEM012MemMerge  #18#18 MemMerge -Type not computed - -[0] → — CTRL -[1] → #9 MEM -[2] → #16 MEM012MemPhi x  #20MEM#2#20 MemPhi x -MEM#2 - -[0] → — ASSOC -[1] → #14 MEM -[2] → #16 MEM012BulkMemPhi except x  #21#21 BulkMemPhi except x -Type not computed - -[0] → — ASSOC -[1] → #17 MEM -[2] → #18 MEM012MemMerge  #22#22 MemMerge -Type not computed - -[0] → — CTRL -[1] → #21 MEM -[2] → #20 MEM012Return  #23#23 Return -Type not computed - -[0] → — CTRL -[1] → #22 MEM -[2] → #5 DATA012 diff --git a/chapter10b/docs/example2c.gv b/chapter10b/docs/example2c.gv deleted file mode 100644 index b32d186d..00000000 --- a/chapter10b/docs/example2c.gv +++ /dev/null @@ -1,15 +0,0 @@ -digraph Memory { - // IR styles match graph/web/render.js; arrows are use -> definition. - graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; - node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; - - rankdir=RL; - - M [label="Prior x memory" shape=octagon style="filled" fillcolor="#dcecff"]; - L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; - S [label="Store p.x" shape=octagon style="filled" fillcolor="#dcecff"]; - L -> M [color="#2867b2"]; S -> M [color="#2867b2"]; - S -> L [style=dashed,xlabel="execute load first"]; - Y [label="Store p.y: independent" shape=octagon style="filled" fillcolor="#dcecff"]; -} diff --git a/chapter10b/docs/example2c.json b/chapter10b/docs/example2c.json deleted file mode 100644 index 380815a2..00000000 --- a/chapter10b/docs/example2c.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "ver": 1, - "comp": "chapter10b/docs/example2c.svg", - "step": 1, - "roots": [13], - "scope": 0, - "nodes": [ - {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, - {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, - {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, - {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 8, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, - {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 10, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 11, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 12, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 10, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null}, - {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter10b/docs/example2c.svg b/chapter10b/docs/example2c.svg deleted file mode 100644 index 01bbe10a..00000000 --- a/chapter10b/docs/example2c.svg +++ /dev/null @@ -1,121 +0,0 @@ -#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #7 · MEM#9[2] → #5 · DATA#10[1] → #7 · MEM#10[2] → #5 · DATA#10[3] → #4 · DATA#11[1] → #8 · MEM#11[2] → #5 · DATA#11[3] → #4 · DATA#12[1] → #3 · MEM#12[2] → #10 · MEM#12[3] → #11 · MEM#13[1] → #12 · MEM#13[2] → #9 · DATAStart  #1#1 Start -Type not computed - -$mem  #3/1#3 $mem -Projection 1 of #1 -Type not computed - -[0] → #1 MEMarg  #4/2#4 arg -Projection 2 of #1 -Type not computed - -[0] → #1 DATAnew Point  #5#5 new Point -Type not computed - -[0] → — CTRL0#0  #6#6 #0 -Type not computed - -[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x -MEM#2 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y -MEM#3 - -[0] → — CTRL -[1] → #3 MEM -[2] → #5 DATA -[3] → #6 DATA0123Load p.x  #9#9 Load p.x -Type not computed - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → — DATA0123Store p.x  #10MEM#2#10 Store p.x -MEM#2 - -[0] → — CTRL -[1] → #7 MEM -[2] → #5 DATA -[3] → #4 DATA0123Store p.y  #11MEM#3#11 Store p.y -MEM#3 - -[0] → — CTRL -[1] → #8 MEM -[2] → #5 DATA -[3] → #4 DATA0123MemMerge  #12#12 MemMerge -Type not computed - -[0] → — CTRL -[1] → #3 MEM -[2] → #10 MEM -[3] → #11 MEM0123Return  #13#13 Return -Type not computed - -[0] → — CTRL -[1] → #12 MEM -[2] → #9 DATA012 diff --git a/chapter10b/docs/lattice.gv b/chapter10b/docs/lattice.gv deleted file mode 100644 index 93d99dd8..00000000 --- a/chapter10b/docs/lattice.gv +++ /dev/null @@ -1,133 +0,0 @@ -digraph Lattice { - rankdir=TB; - newrank=true; - // Preserve the same-rank ordering edges through cluster layout. - remincross=false; - nodesep=0.22; - ranksep=0.65; - node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; - edge [color="#555555", arrowsize=0.6]; - - // These arrows are lattice-order edges, not use/definition edges. - TOP [label="⊤", fillcolor=black, fontcolor=white]; - BOT [label="⊥", fillcolor=black, fontcolor=white]; - - subgraph cluster_control { - label="Control"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffff00"]; - XCTRL [label="⊤:ctrl"]; - CTRL [label="⊥:ctrl"]; - XCTRL -> CTRL; - } - - subgraph cluster_integer { - label="Integer"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#add8e6"]; - INT_TOP [label="⊤:int"]; - INT_BOT [label="⊥:int"]; - INT_NEG [label="… −2, −1"]; - INT_ZERO [label="0"]; - INT_POS [label="1, 2, …"]; - {rank=same; INT_NEG; INT_ZERO; INT_POS;} - INT_NEG -> INT_ZERO -> INT_POS [style=invis]; - INT_TOP -> {INT_NEG INT_ZERO INT_POS}; - {INT_NEG INT_ZERO INT_POS} -> INT_BOT; - } - - subgraph cluster_pointer { - label="Pointer = nullability × struct type"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#008000", fontcolor=white]; - PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; - PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; - PTR_NULL [label="null"]; - PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; - PTR_PERSON [label="*Person"]; - PTR_S1 [label="*s1"]; - PTR_MORE [label="*s2, …"]; - PTR_PERSON_NIL [label="*Person?"]; - PTR_S1_NIL [label="*s1?"]; - PTR_MORE_NIL [label="*s2?, …"]; - {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} - {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} - PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; - PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; - PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; - {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; - PTR_PERSON -> PTR_PERSON_NIL; - PTR_S1 -> PTR_S1_NIL; - PTR_MORE -> PTR_MORE_NIL; - PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; - {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; - } - - subgraph cluster_struct { - label="Struct\nnamed collection of named scalar types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#90ee90"]; - STRUCT_TOP [label="⊤:struct\n[struct top]"]; - STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; - STRUCT_PERSON [label="Person\nage: int", shape=box]; - STRUCT_S1 [label="s1\nx: int", shape=box]; - STRUCT_S2 [label="s2\nx: int, y: int", shape=box]; - STRUCT_MORE [label="…", shape=box]; - {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} - STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; - STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; - {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; - } - - subgraph cluster_memory { - label="Memory\nprecise alias slices"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#00008b", fontcolor=white]; - MEM_TOP [label="⊤:mem"]; - MEM_BOT [label="⊥:mem"]; - MEM_ALIAS2 [label="MEM#2"]; - MEM_ALIAS3 [label="MEM#3"]; - MEM_MORE [label="…"]; - {rank=same; MEM_ALIAS2; MEM_ALIAS3; MEM_MORE;} - MEM_ALIAS2 -> MEM_ALIAS3 -> MEM_MORE [style=invis]; - MEM_TOP -> {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE}; - {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE} -> MEM_BOT; - } - - subgraph cluster_tuple { - label="Tuple\ncollection of types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffc0cb"]; - TUPLE_TOP [label="⊤:tuple"]; - TUPLE_BOT [label="⊥:tuple"]; - // Each black element stands for a recursively nested type lattice. - TUPLE1 [shape=plain, label=< - - - -
tuple
type
- >]; - TUPLE2 [shape=plain, label=< - - - - -
tuple
type
type
- >]; - TUPLEN [shape=plain, label=< - - - - - -
tuple
type
…
type
- >]; - {rank=same; TUPLE1; TUPLE2; TUPLEN;} - TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; - TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; - {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; - } - - TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; - {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; - {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} - {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} - XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; -} diff --git a/chapter10b/docs/lattice.svg b/chapter10b/docs/lattice.svg deleted file mode 100644 index 8d2e2875..00000000 --- a/chapter10b/docs/lattice.svg +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - -Lattice - - -cluster_control - -Control - - -cluster_integer - -Integer - - -cluster_pointer - -Pointer = nullability × struct type - - -cluster_struct - -Struct -named collection of named scalar types - - -cluster_memory - -Memory -precise alias slices - - -cluster_tuple - -Tuple -collection of types - - - -TOP - -⊤ - - - -XCTRL - -⊤:ctrl - - - -TOP->XCTRL - - - - - -INT_TOP - -⊤:int - - - -TOP->INT_TOP - - - - - -PTR_TOP - -⊤:ptr -[*⊤:struct] - - - -TOP->PTR_TOP - - - - - -STRUCT_TOP - -⊤:struct -[struct top] - - - -TOP->STRUCT_TOP - - - - - -MEM_TOP - -⊤:mem - - - -TOP->MEM_TOP - - - - - -TUPLE_TOP - -⊤:tuple - - - -TOP->TUPLE_TOP - - - - - -BOT - -⊥ - - - -CTRL - -⊥:ctrl - - - -XCTRL->CTRL - - - - - - -CTRL->BOT - - - - - -INT_NEG - -… −2, −1 - - - -INT_TOP->INT_NEG - - - - - -INT_ZERO - -0 - - - -INT_TOP->INT_ZERO - - - - - -INT_POS - -1, 2, … - - - -INT_TOP->INT_POS - - - - - - -INT_BOT - -⊥:int - - - -INT_BOT->BOT - - - - - -INT_NEG->INT_BOT - - - - - - -INT_ZERO->INT_BOT - - - - - - -INT_POS->INT_BOT - - - - - -PTR_NULL - -null - - - -PTR_TOP->PTR_NULL - - - - - -PTR_PERSON - -*Person - - - -PTR_TOP->PTR_PERSON - - - - - -PTR_S1 - -*s1 - - - -PTR_TOP->PTR_S1 - - - - - -PTR_MORE - -*s2, … - - - -PTR_TOP->PTR_MORE - - - - - - -PTR_BOT - -⊥:ptr -[*⊥:struct?] - - - -PTR_BOT->BOT - - - - - -PTR_PERSON_NIL - -*Person? - - - -PTR_NULL->PTR_PERSON_NIL - - - - - -PTR_S1_NIL - -*s1? - - - -PTR_NULL->PTR_S1_NIL - - - - - -PTR_MORE_NIL - -*s2?, … - - - -PTR_NULL->PTR_MORE_NIL - - - - - -PTR_ANY - -*⊥:struct -[ptr any struct] - - - -PTR_ANY->PTR_BOT - - - - - - -PTR_PERSON->PTR_ANY - - - - - - -PTR_PERSON->PTR_PERSON_NIL - - - - - -PTR_S1->PTR_ANY - - - - - - -PTR_S1->PTR_S1_NIL - - - - - - -PTR_MORE->PTR_ANY - - - - - -PTR_MORE->PTR_MORE_NIL - - - - - -PTR_PERSON_NIL->PTR_BOT - - - - - - -PTR_S1_NIL->PTR_BOT - - - - - - -PTR_MORE_NIL->PTR_BOT - - - - - -STRUCT_PERSON - -Person -age: int - - - -STRUCT_TOP->STRUCT_PERSON - - - - - -STRUCT_S1 - -s1 -x: int - - - -STRUCT_TOP->STRUCT_S1 - - - - - -STRUCT_S2 - -s2 -x: int, y: int - - - -STRUCT_TOP->STRUCT_S2 - - - - - -STRUCT_MORE - -… - - - -STRUCT_TOP->STRUCT_MORE - - - - - - -STRUCT_BOT - -⊥:struct -[struct bottom] - - - -STRUCT_BOT->BOT - - - - - -STRUCT_PERSON->STRUCT_BOT - - - - - - -STRUCT_S1->STRUCT_BOT - - - - - - -STRUCT_S2->STRUCT_BOT - - - - - - -STRUCT_MORE->STRUCT_BOT - - - - - -MEM_ALIAS2 - -MEM#2 - - - -MEM_TOP->MEM_ALIAS2 - - - - - -MEM_ALIAS3 - -MEM#3 - - - -MEM_TOP->MEM_ALIAS3 - - - - - -MEM_MORE - -… - - - -MEM_TOP->MEM_MORE - - - - - - -MEM_BOT - -⊥:mem - - - -MEM_BOT->BOT - - - - - -MEM_ALIAS2->MEM_BOT - - - - - - -MEM_ALIAS3->MEM_BOT - - - - - - -MEM_MORE->MEM_BOT - - - - - -TUPLE1 - - - -tuple - - -type - - - -TUPLE_TOP->TUPLE1 - - - - - -TUPLE2 - - - -tuple - - -type - - -type - - - -TUPLE_TOP->TUPLE2 - - - - - -TUPLEN - - - -tuple - - -type - - -… - - -type - - - -TUPLE_TOP->TUPLEN - - - - - -TUPLE_BOT - -⊥:tuple - - - -TUPLE_BOT->BOT - - - - - -TUPLE1->TUPLE_BOT - - - - - - -TUPLE2->TUPLE_BOT - - - - - - -TUPLEN->TUPLE_BOT - - - - - diff --git a/chapter10b/pom.xml b/chapter10b/pom.xml deleted file mode 100644 index 5378c5eb..00000000 --- a/chapter10b/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.seaofnodes - simple - 1.0 - - chapter10b - jar - Chapter 10b - - ${project.basedir}/../graph - ${project.basedir}/../print - - diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java deleted file mode 100644 index d5ab15fe..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.print.IRAdapter; -import com.seaofnodes.simple.node.*; - -/** This chapter's read-only IR facts and the familiar debugger entry points. */ -public final class IRPrinter extends IRAdapter { - private static final com.seaofnodes.print.IRPrinter PRINT = - new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); - @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } - @Override public int inputColumns() { return 3; } - @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } - @Override public Kind kind(Node n) { - if( n instanceof StartNode ) return Kind.START; - if( n instanceof StopNode ) return Kind.STOP; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof ReturnNode ) return Kind.RETURN; - if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof ProjNode ) return n.isCFG() ? Kind.CPROJ : Kind.PROJ; - if( n.isCFG() ) return Kind.CTRL; - if( n instanceof MultiNode ) return Kind.MULTI; - return Kind.DATA; - } - @Override public int index(Node n) { - if( n instanceof ProjNode p ) return p._idx; - return n._nid; - } - - public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index 7075d526..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Random; - -/** - * The IterOptim runs after parsing. It iterates the peepholes to a fixed point - * so that no more peepholes apply. This should be linear because peepholes rarely - * (never?) increase code size. The graph should monotonically reduce in some - * dimension, which is usually size. It might also reduce in e.g. number of - * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper - * ones. - * - * The theoretical overall worklist is mindless just grabbing the next thing and - * doing it. If the graph changes, put the neighbors on the worklist. Lather, - * Rinse, Repeat until the worklist runs dry. - * - * The main issues we have to deal with: - * - *
    - *
  • Nodes have uses; replacing some set of Nodes with another requires more graph - * reworking. Not rocket science, but it can be fiddly. Its helpful to have a - * small set of graph munging utilities, and the strong invariant that the graph - * is stable and correct between peepholes. In our case `Node.subsume` does - * most of the munging, building on our prior stable Node utilities.
  • - * - *
  • Changing a Node also changes the graph "neighborhood". The neigbors need to - * be checked to see if THEY can also peephole, and so on. After any peephole - * or graph update we put a Nodes uses and defs on the worklist.
  • - * - *
  • Our strong invariant is that for all Nodes, either they are on the worklist - * OR no peephole applies. This invariant is easy to check, although expensive. - * Basically the normal "iterate peepholes to a fixed point" is linear, and this - * check is linear at each peephole step... so quadratic overall. Its a useful - * assert, but one we can disable once the overall algorithm is stable - and - * then turn it back on again when some new set of peepholes is misbehaving. - * The code for this is turned on in `IterOptim.iterate` as `assert - * progressOnList(stop);`
  • - *
- */ -public abstract class IterPeeps { - - private static final WorkList WORK = new WorkList<>(); - - public static N add( N n ) { - return (N)WORK.push(n); - } - - public static void addAll( ArrayList ary ) { - WORK.addAll(ary); - } - - /** - * Iterate peepholes to a fixed point - */ - public static StopNode iterate(StopNode stop) { - assert progressOnList(stop); - int cnt=0; - - Node n; - 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() ) { - 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 - if( x != n || !(x instanceof ConstantNode) ) { - // All outputs of n (changing node) not x (prior existing node). - for( Node z : n._outputs ) WORK.push(z); - // Everybody gets a free "go again" in case they didn't get - // made in their final form. - WORK.push(x); - // If the result is not self, revisit all inputs (because - // there's a new user), and replace in the graph. - if( x != n ) { - for( Node z : n. _inputs ) WORK.push(z); - n.subsume(x); - } - } - // If there are distant neighbors, move to worklist - n.moveDepsToWorklist(); - 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); - } - - return stop; - } - - // Visit ALL nodes and confirm the invariant: - // Either you are on the WORK worklist OR running `iter()` makes no progress. - - // This invariant ensures that no progress is missed, i.e., when the - // worklist is empty we have indeed done all that can be done. To help - // with debugging, the {@code assert} is broken out in a place where it is easy to - // stop if a change is found. - - // Also, the normal usage of `iter()` may attempt peepholes with distance - // neighbors and these should fail, but will then try to add dependencies - // {@link #Node.addDep} which is a side effect in an assert. The {@link - // #midAssert} is used to stop this side effect. - private static boolean MID_ASSERT; - public static boolean midAssert() { return MID_ASSERT; } - private static boolean progressOnList(Node stop) { - MID_ASSERT = true; - int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; - Node changed = stop.walk( n -> { - if( WORK.on(n) ) return null; - Node m = n.peepholeOpt(); - if( m==null ) return null; - System.err.println("BREAK HERE FOR BUG"); - return m; - }); - Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop; - MID_ASSERT = false; - return changed==null; - } - - public static void reset() { - WORK.clear(); - } - - /** - * Classic WorkList, with a fast add/remove, dup removal, random pull. - * The Node's nid is used to check membership in the worklist. - */ - public static class WorkList { - - private Node[] _es; - private int _len; - private final BitSet _on; // Bit set if Node._nid is on WorkList - private final Random _R; // For randomizing pull from the WorkList - private final long _seed; - - /* Useful stat - how many nodes are processed in the post parse iterative opt */ - private long _totalWork = 0; - - WorkList() { this(123); } - WorkList(long seed) { - _es = new Node[1]; - _len=0; - _on = new BitSet(); - _seed = seed; - _R = new Random(); - _R.setSeed(_seed); - } - - /** - * Pushes a Node on the WorkList, ensuring no duplicates - * If Node is null it will not be added. - */ - public E push( E x ) { - if( x==null ) return null; - int idx = x._nid; - if( !_on.get(idx) ) { - _on.set(idx); - if( _len==_es.length ) - _es = Arrays.copyOf(_es,_len<<1); - _es[_len++] = x; - _totalWork++; - } - return x; - } - - public void addAll( ArrayList ary ) { - for( E n : ary ) - push(n); - } - - /** - * True if Node is on the WorkList - */ - boolean on( E x ) { return _on.get(x._nid); } - - /** - * Removes a random Node from the WorkList; null if WorkList is empty - */ - E pop() { - if( _len == 0 ) return null; - int idx = _R.nextInt(_len); - E x = (E)_es[idx]; - _es[idx] = _es[--_len]; // Compress array - _on.clear(x._nid); - return x; - } - - public void clear() { - _len = 0; - _on.clear(); - _R.setSeed(_seed); - _totalWork = 0; - } - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10b/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 3b5f2721..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,843 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.graph.GraphObserver; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; - -import java.util.*; - -/** - * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract - * Syntax Tree structure. - *

- * This is a simple recursive descent parser. All lexical analysis is done here as well. - */ -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 - * through the entire parser and optimizer. - *

- * To make the compiler multithreaded, this field will have to move into a TLS. - */ - public static StartNode START; - - public StopNode STOP; - - // Field identities are independent of the memory graph and Start's tuple. - private final HashMap _aliases = new HashMap<>(); - private int _alias = 2; - - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. - private final Lexer _lexer; - - /** - * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time - * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how - * we handle branching. See {@link #parseIf()}. - *

- * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds - * variable names to Nodes. The top of this stack represents current scope. - *

- * We keep a list of all ScopeNodes so that we can show them in graphs. - * @see #parseIf() - * @see #_xScopes - */ - public ScopeNode _scope; - - /** - * List of keywords disallowed as identifiers - */ - private static final HashSet KEYWORDS = new HashSet<>(){{ - add("break"); - add("continue"); - add("else"); - add("false"); - add("if"); - add("int"); - add("new"); - add("null"); - add("return"); - add("struct"); - add("true"); - add("while"); - }}; - - - /** - * We clone ScopeNodes when control flows branch; it is useful to have - * a list of all active ScopeNodes for purposes of visualization of the SoN graph - */ - public final Stack _xScopes = new Stack<>(); - - ScopeNode _continueScope; - ScopeNode _breakScope; - - // Mapping from a Struct name to a Struct. - public static Map OBJS = new HashMap<>(); - - public Parser(String source, TypeInteger arg) { - PARSER = this; - Node.reset(); - IterPeeps.reset(); - OBJS.clear(); - _lexer = new Lexer(source); - _scope = new ScopeNode(); - _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); - STOP = new StopNode(source); - } - - public Parser(String source) { - this(source, TypeInteger.BOT); - } - - @Override - public String toString() { return _lexer.toString(); } - - String src() { return new String( _lexer._input ); } - - // Debugging utility to find a Node by index - public static Node find(int nid) { return START.find(nid); } - - private Node ctrl() { return _scope.ctrl(); } - - private Node ctrl(Node n) { return _scope.ctrl(n); } - - public StopNode parse() { - _xScopes.push(_scope); - // Enter a new scope for the initial control and arguments - _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); - _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); - parseBlock(); - _scope.pop(); - _xScopes.pop(); - if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); - STOP.peephole(); - return STOP; - } - - /** - * Parses a block - * - *

-     *     '{' statements '}'
-     * 
- * Does not parse the opening or closing '{}' - * @return a {@link Node} or {@code null} - */ - private Node parseBlock() { - // Enter a new scope - _scope.push(); - while (!peek('}') && !_lexer.isEOF()) - parseStatement(); - // Exit scope - _scope.pop(); - return null; - } - - /** - * Parses a statement - * - *
-     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
-     * 
- * @return a {@link Node} or {@code null} - */ - private Node parseStatement() { - if( false ) return null; - else if (matchx("return") ) return parseReturn(); - else if (matchx("int") ) return parseDecl(TypeInteger.BOT); - else if (match ("{") ) return require(parseBlock(),"}"); - else if (matchx("if") ) return parseIf(); - else if (matchx("while") ) return parseWhile(); - else if (matchx("break") ) return parseBreak(); - else if (matchx("continue")) return parseContinue(); - else if (matchx("struct") ) return parseStruct(); - else if (matchx(";") ) return null; // Empty statement - // declarations of vars with struct type are handled in parseExpressionStatement due - // to ambiguity - else return parseExpressionStatement(); - } - - /** - * Parse a struct field. - *
-     *     int IDENTIFIER ;
-     * 
- */ - private Field parseField(String sname) { - if( matchx("int") ) // Currently only parsing "int" type fields - return require(Field.make(requireId().intern(),TypeInteger.BOT),";"); - throw errorSyntax("A field type is expected, only type 'int' is supported at present"); - } - - /** - * Parse a struct declaration, and return the following statement. - * Only allowed in top level scope. - * Structs cannot be redefined. - * - * @return The statement following the struct - */ - private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); - String typeName = requireId(); - if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); - ArrayList fields = new ArrayList<>(); - require("{"); - while (!peek('}') && !_lexer.isEOF()) { - Field field = parseField(typeName); - if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); - fields.add(field); - } - require("}"); - // Build and install the TypeStruct - TypeStruct ts = TypeStruct.make(typeName, fields); - _aliases.put(typeName,_alias); - _alias += ts._fields.length; - OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names - return parseStatement(); - } - - /** - * Parses a while statement - * - *
-     *     while ( expression ) statement
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseWhile() { - - var savedContinueScope = _continueScope; - var savedBreakScope = _breakScope; - - require("("); - - // Loop region has two control inputs, the first is the entry - // point, and second is back edge that is set after loop is parsed - // (see end_loop() call below). Note that the absence of back edge is - // used as an indicator to switch off peepholes of the region and - // associated phis; see {@code inProgress()}. - - ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here - - // At loop head, we clone the current Scope (this includes all - // names in every nesting level within the Scope). - // We create phis eagerly for all the names we find, see dup(). - - // Save the current scope as the loop head - ScopeNode head = _scope.keep(); - // Clone the head Scope to create a new Scope for the body. - // Create phis eagerly as part of cloning - _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis - - // Parse predicate - var pred = require(parseExpression(), ")"); - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); - - // Clone the body Scope to create the break/exit Scope which accounts for any - // side effects in the predicate. The break/exit Scope will be the final - // scope after the loop, and its control input is the False branch of - // the loop predicate. Note that body Scope is still our current scope. - ctrl(ifF); - _xScopes.push(_breakScope = _scope.dup()); - - // No continues yet - _continueScope = null; - - // Parse the true side, which corresponds to loop body - // Our current scope is the body Scope - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - parseStatement(); // Parse loop body - - // Merge the loop bottom into other continue statements - if (_continueScope != null) { - _continueScope = jumpTo(_continueScope); - _scope.kill(); - _scope = _continueScope; - } - - // The true branch loops back, so whatever is current _scope.ctrl gets - // added to head loop as input. endLoop() updates the head scope, and - // goes through all the phis that were created earlier. For each phi, - // it sets the second input to the corresponding input from the back - // edge. If the phi is redundant, it is replaced by its sole input. - var exit = _breakScope; - head.endLoop(_scope, exit); - head.unkeep().kill(); - - _xScopes.pop(); // Cleanup - _xScopes.pop(); // Cleanup - - _continueScope = savedContinueScope; - _breakScope = savedBreakScope; - - // At exit the false control is the current control, and - // the scope is the exit scope after the exit test. - return _scope = exit; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope - // Prune nested lexical scopes that have depth > than the loop head - // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur._scopes.size() > _breakScope._scopes.size() ) - cur.pop(); - // If this is a continue then first time the target is null - // So we just use the pruned current scope as the base for the - // "continue" - if (toScope == null) - return cur; - // toScope is either the break scope, or a scope that was created here - assert toScope._scopes.size() <= _breakScope._scopes.size(); - toScope.ctrl(toScope.mergeScopes(cur)); - return toScope; - } - - private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } - - private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); } - private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } - - /** - * Parses a statement - * - *
-     *     if ( expression ) statement [else statement]
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseIf() { - require("("); - // Parse predicate - var pred = require(parseExpression(), ")").keep(); - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); - // In if true branch, the ifT proj node becomes the ctrl - // But first clone the scope and set it as current - int ndefs = _scope.nIns(); - ScopeNode fScope = _scope.dup(); // Duplicate current scope - fScope.ctrl(ifF); // The pending arm already starts at False - _xScopes.push(fScope); // For graph visualization we need all scopes - - // Parse the true side - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.upcast(ifT,pred,false); // Up-cast predicate - parseStatement(); // Parse true-side - ScopeNode tScope = _scope; - - // Parse the false side - _scope = fScope; // Restore scope, then parse else block if any - ifF.unkeep(); // fScope already owns the false control - if (matchx("else")) { - _scope.upcast(ifF,pred,true); // Up-cast predicate - parseStatement(); - fScope = _scope; - } - pred.unkeep(); - - if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) - throw error("Cannot define a new name on one arm of an if"); - - // Merge results - _scope = tScope; - _xScopes.pop(); // Discard pushed from graph display - - return ctrl(tScope.mergeScopes(fScope)); - } - - - /** - * Parses a return statement; "return" already parsed. - * The $ctrl edge is killed. - * - *
-     *     'return' expr ;
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseReturn() { - var expr = require(parseExpression(), ";"); - Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); - ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control - return ret; - } - - - - /** - * Parses an expression statement or a declaration statement where type is a struct - * - *
-     *      name;         // Error
-     * type name;         // Define name with default initial value
-     * type name = expr;  // Define name with given   initial value
-     *      name = expr;  // Reassign existing
-     *             expr   // Something else
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpressionStatement() { - int old = _lexer._position; - Type t = type(); - String name = requireId(); - Node expr; - if( match(";") ) { // Assign a default value - // No type and no expr is an error - if( t==null ) throw errorSyntax("expression"); - expr = new ConstantNode(t.makeInit()).peephole(); - } else if( match("=") ) { // Assign "= expr;" - expr = require(parseExpression(), ";"); - } else { // Neither, so just a normal expression parse - _lexer._position = old; - return require(parseExpression(),";"); - } - - // Defining a new variable vs updating an old one - if( t != null ) { - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - } else { - Node n = _scope.lookup(name); - t = _scope.lookupDeclaredType(name); - if( n==null ) - throw error("Undefined name '" + name + "'"); - _scope.update(name,expr); - } - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - return expr; - } - - // Parse a type-or-null - private Type type() { - int old = _lexer._position; - String tname = _lexer.matchId(); - if( tname==null ) return null; - if( tname.equals("int") ) return TypeInteger.BOT; - TypeStruct obj = OBJS.get(tname); - if( obj != null ) - return TypeMemPtr.make(obj,match("?")); - // Not a type; unwind the parse - _lexer._position = old; - return null; - } - - /** - * Parses a declStatement - * - *
-     *     type name = expression ';'
-     * 
- * @return an expression {@link Node} - */ - private Node parseDecl(Type t) { - var name = requireId(); - var expr = match(";") - // Assign a null value - ? new ConstantNode(t.makeInit()).peephole() - // Assign "= expr;" - : require(require("=").parseExpression(), ";"); - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - return expr; - } - - - /** - * Parse an expression of the form: - * - *
-     *     expr : compareExpr
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpression() { return parseComparison(); } - - /** - * Parse an expression of the form: - * - *
-     *     expr : additiveExpr op additiveExpr
-     * 
- * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - var lhs = parseAddition(); - while( true ) { - int idx=0; boolean negate=false; - // Test for any local nodes made, and "keep" lhs during peepholes - if( false ) ; - else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } - else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } - else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } - else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } - else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } - else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } - else break; - // Peepholes can fire, but lhs is already "hooked", kept alive - lhs.setDef(idx,parseAddition()); - lhs = lhs.peephole(); - if( negate ) // Extra negate for != - lhs = new NotNode(lhs).peephole(); - } - return lhs; - } - - /** - * Parse an additive expression - * - *
-     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
-     * 
- * @return an add expression {@link Node}, never {@code null} - */ - private Node parseAddition() { - Node lhs = parseMultiplication(); - while( true ) { - if( false ) ; - else if( match("+") ) lhs = new AddNode(lhs,null); - else if( match("-") ) lhs = new SubNode(lhs,null); - else break; - lhs.setDef(2,parseMultiplication()); - lhs = lhs.peephole(); - } - return lhs; - } - - /** - * Parse an multiplicativeExpr expression - * - *
-     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
-     * 
- * @return a multiply expression {@link Node}, never {@code null} - */ - private Node parseMultiplication() { - var lhs = parseUnary(); - while( true ) { - if( false ) ; - else if( match("*") ) lhs = new MulNode(lhs,null); - else if( match("/") ) lhs = new DivNode(lhs,null); - else break; - lhs.setDef(2,parseUnary()); - lhs = lhs.peephole(); - } - return lhs; - } - - /** - * Parse a unary minus expression. - * - *
-     *     unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
-     * 
- * @return a unary expression {@link Node}, never {@code null} - */ - private Node parseUnary() { - if (match("-")) return new MinusNode(parseUnary()).peephole(); - if (match("!")) return new NotNode(parseUnary()).peephole(); - return parsePostfix(parsePrimary()); - } - - /** - * Parse a primary expression: - * - *
-     *     primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')'
-     * 
- * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { - if( _lexer.isNumber() ) return parseIntegerLiteral(); - if( match("(") ) return require(parseExpression(), ")"); - if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); - if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); - if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); - if( matchx("new") ) { - String structName = requireId(); - TypeStruct obj = OBJS.get(structName); - if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'"); - return newStruct(obj); - } - String name = _lexer.matchId(); - if( name == null) throw errorSyntax("an identifier or expression"); - Node n = _scope.lookup(name); - if( n!=null ) return n; - throw error("Undefined name '" + name + "'"); - } - - /** - * Return a NewNode but also generate instructions to initialize it. - */ - private Node newStruct( TypeStruct obj ) { - Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); - int alias = _aliases.get(obj._name); - for( Field field : obj._fields ) { - store(field._fname,alias,n,initValue); - alias++; - } - return n.unkeep(); - } - - // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.lookup("$mem"); } - private Node mem(Node n) { return _scope.update("$mem",n); } - - private void store(String name, int alias, Node ptr, Node val) { - Node prior = mem().keep(); - Node st = new StoreNode(name,alias,prior,ptr,val).peephole(); - mem(new MemMergeNode(prior,alias,st).peephole()); - prior.unkeep(); - } - - /** - * Parse postfix expression. For now this is just a field - * expression, but in future could be array index too. - * - *
-     *     expr ('.' IDENTIFIER)* [ = expr ]
-     * 
- */ - private Node parsePostfix(Node expr) { - if( !match(".") ) return expr; - - if( !(expr._type instanceof TypeMemPtr ptr) ) - throw error("Expected struct reference but got " + expr._type.str()); - - String name = requireId().intern(); - int idx = ptr._obj==null ? -1 : ptr._obj.find(name); - if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - int alias = _aliases.get(ptr._obj._name)+idx; - - if( match("=") ) { - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( peek('=') ) _lexer._position--; - else { - Node val = parseExpression(); - store(name,alias,expr,val); - return expr; // "obj.a = expr" returns the expression while updating memory - } - } - - Type declaredType = ptr._obj._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType, mem(), expr).peephole()); - } - - /** - * Parse integer literal - * - *
-     *     integerLiteral: [1-9][0-9]* | [0]
-     * 
- */ - private ConstantNode parseIntegerLiteral() { - return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); - } - - ////////////////////////////////// - // Utilities for lexical analysis - - // Return true and skip if "syntax" is next in the stream. - private boolean match (String syntax) { return _lexer.match (syntax); } - // Match must be "exact", not be followed by more id letters - private boolean matchx(String syntax) { return _lexer.matchx(syntax); } - // Return true and do NOT skip if 'ch' is next - private boolean peek(char ch) { return _lexer.peek(ch); } - private boolean peekIsId() { return _lexer.peekIsId(); } - - // Require and return an identifier - private String requireId() { - String id = _lexer.matchId(); - if (id != null && !KEYWORDS.contains(id) ) return id; - throw error("Expected an identifier, found '"+id+"'"); - } - - - - // Require an exact match - private Parser require(String syntax) { require(null, syntax); return this; } - private N require(N n, String syntax) { - if (match(syntax)) return n; - throw errorSyntax(syntax); - } - - RuntimeException errorSyntax(String syntax) { - return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); - } - - public static RuntimeException error( String errorMessage ) { - return new RuntimeException(errorMessage); - } - - //////////////////////////////////// - // Lexer components - - private static class Lexer { - - // Input buffer; an array of text bytes read from a file or a string - private final byte[] _input; - // Tracks current position in input buffer - private int _position = 0; - - /** - * Record the source text for lexing - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - public Lexer(byte[] buf) { - _input = buf; - } - - // Very handy in the debugger, shows the unparsed program - @Override - public String toString() { - return new String(_input, _position, _input.length - _position); - } - - // True if at EOF - private boolean isEOF() { - return _position >= _input.length; - } - - // Peek next character, or report EOF - private char peek() { - return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate - : (char) _input[_position]; - } - - private char nextChar() { - char ch = peek(); - _position++; - return ch; - } - - // True if a white space - private boolean isWhiteSpace() { - return peek() <= ' '; // Includes all the use space, tab, newline, CR - } - - /** - * Return the next non-white-space character - */ - private void skipWhiteSpace() { - while (isWhiteSpace()) _position++; - } - - - // Return true, if we find "syntax" after skipping white space; also - // then advance the cursor past syntax. - // Return false otherwise, and do not advance the cursor. - boolean match(String syntax) { - skipWhiteSpace(); - int len = syntax.length(); - if (_position + len > _input.length) return false; - for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) - return false; - _position += len; - return true; - } - - // Match must be exact and not followed by more ID characters. - // Prevents identifier "ifxy" from matching an "if" statement. - boolean matchx(String syntax) { - if( !match(syntax) ) return false; - if( !isIdLetter(peek()) ) return true; - _position -= syntax.length(); - return false; - } - - private boolean peek(char ch) { - skipWhiteSpace(); - return peek()==ch; - } - - boolean peekIsId() { - skipWhiteSpace(); - return isIdStart(peek()); - } - - // Return an identifier or null - String matchId() { - return peekIsId() ? parseId() : null; - } - - // Used for errors - String getAnyNextToken() { - if (isEOF()) return ""; - if (isIdStart(peek())) return parseId(); - if (isNumber(peek())) return parseNumberString(); - if (isPunctuation(peek())) return parsePunctuation(); - return String.valueOf(peek()); - } - - boolean isNumber() {return isNumber(peek());} - boolean isNumber(char ch) {return Character.isDigit(ch);} - - private Type parseNumber() { - String snum = parseNumberString(); - if (snum.length() > 1 && snum.charAt(0) == '0') - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(snum)); - } - private String parseNumberString() { - int start = _position; - while (isNumber(nextChar())) ; - return new String(_input, start, --_position - start); - } - - // First letter of an identifier - private boolean isIdStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - // All characters of an identifier, e.g. "_x123" - private boolean isIdLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private String parseId() { - int start = _position; - while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); - } - - private boolean isPunctuation(char ch) { - return "=;[]<>(){}+-/*!".indexOf(ch) != -1; - } - - private String parsePunctuation() { - int start = _position; - return new String(_input, start, 1); - } - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java deleted file mode 100644 index 4f2af769..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple; - -import java.util.ArrayList; - -public class Utils { - public static RuntimeException TODO() { return TODO("Not yet implemented"); } - public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } - - /** - * Fast, constant-time, element removal. Does not preserve order - * - * @param array ArrayList to modify - * @param i element to be removed - * @return element removed - */ - public static E del(ArrayList array, int i) { - if ( i >= 0 && i < array.size() ) { - E tmp = array.get(i); - E last = array.removeLast(); - if (i < array.size()) array.set(i, last); - return tmp; - } - return null; - } - - /** - * Search a list for an element by reference - * - * @param ary List to search in - * @param x Object to be searched - * @return >= 0 on success, -1 on failure - */ - public static int find( ArrayList ary, E x ) { - for( int i=0; i= 0 on success, -1 on failure - */ - public static int find( E[] ary, E x ) { - for( int i=0; i>>n); } - - // Fold a 64bit hash into 32 bits - public static int fold( long x ) { return (int)((x>>32) ^ x); } - - public static boolean eq(String s0, String s1) { - if( s0==s1 ) return true; - assert s0==s0.intern(); - assert s1==s1.intern(); - return false; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index e235d288..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.*; - - -public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Add"; } - - @Override protected String format() { return "(%1+%2)"; } - - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(i0.value()+i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize () { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Add of 0. We do not check for (0+x) because this will already - // canonicalize to (x+0) - if( t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Add of same to a multiply by 2 - if( lhs==rhs ) - return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); - - // Goal: a left-spine set of adds, with constants on the rhs (which then fold). - - // Move non-adds to RHS - if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) - return swap12(); - - // x+(-y) becomes x-y - if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); - - // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) - - // Do we have x + (y + z) ? - // Swap to (x + y) + z - // Rotate (add add add) to remove the add on RHS - if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); - - // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) - // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); - - // Now we only see (add add non) - - // Dead data cycle; comes about from dead infinite loops. Do nothing, - // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) - return null; - - // Do we have (x + con1) + con2? - // Replace with (x + (con1+con2) which then fold the constants - // lhs.in(2) is con1 here - // If lhs.in(2) is not a constant, we add ourselves as a dependency - // because if it later became a constant then we could make this - // transformation. - if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); - - - // Do we have ((x + (phi cons)) + con) ? - // Do we have ((x + (phi cons)) + (phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - Node phicon = phiCon(this,true); - if( phicon!=null ) return phicon; - - // Now we sort along the spine via rotates, to gather similar things together. - - // Do we rotate (x + y) + z - // into (x + z) + y ? - if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - - return null; - } - - // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - static Node phiCon(Node op, boolean rotate) { - Node lhs = op.in(1); - Node rhs = op.in(2); - // LHS is either a Phi of constants, or another op with Phi of constants - PhiNode lphi = pcon(lhs,op); - if( rotate && lphi==null && lhs.nIns() > 2 ) { - // Only valid to rotate constants if both are same associative ops - if( lhs.getClass() != op.getClass() ) return null; - lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push - } - if( lphi==null ) return null; - - // RHS is a constant or a Phi of constants - if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) - return null; - - // If both are Phis, must be same Region - if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) - return null; - - // Note that this is the exact reverse of Phi pulling a common op down - // to reduce total op-count. We don't get in an endless push-up - // push-down peephole cycle because the constants all fold first. - Node[] ns = new Node[lphi.nIns()]; - ns[0] = lphi.in(0); - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - for( int i=1; i hi._nid; - } - - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index 63452dba..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; - - -abstract public class BoolNode extends Node { - - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - abstract String op(); // String opcode name - - @Override - public String label() { return getClass().getSimpleName(); } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.open().n(in(1)).p(op()).n(in(2)).close(); - } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); - } - return TypeInteger.BOT; - } - - abstract boolean doOp(long lhs, long rhs); - - @Override - public Node idealize() { - // Compare of same - if( in(1)==in(2) ) - return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); - - // Equals pushes constant to the right; 5==X becomes X==5. - if( this instanceof EQ ) { - if( !(in(2) instanceof ConstantNode) ) { - // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode ) - return new EQ(in(2),in(1)); - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - else if( in(1)._nid > in(2)._nid ) - return new EQ(in(2),in(1)); - } - // Equals X==0 becomes a !X - if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) - return new NotNode(in(1)); - } - - // Do we have ((phi cons) cmp con) ? - // Do we have ((phi cons) cmp (phi cons)) ? - // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) - Node phicon = AddNode.phiCon(this,this instanceof EQ); - if( phicon!=null ) return phicon; - - return null; - } - - public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } - public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } - public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java deleted file mode 100644 index 8d512f21..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import java.util.BitSet; - -/** - * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; - * those aliases must be represented by parallel precise MemPhis at the same - * program slice. - */ -public class BulkMemPhiNode extends PhiNode { - - private static final BitSet EMPTY = new BitSet(); - - // Excluded Aliases; empty means NO exclusions. - // Each split clones the exclusions; published sets are never mutated. - public final BitSet _aliases; - - public BulkMemPhiNode(String label, Node... inputs) { - this(label,EMPTY,inputs); - } - - private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label,TypeMem.BOT,inputs); - _aliases = aliases; - } - - public boolean isSplit(int alias) { return _aliases.get(alias); } - - @Override public String label() { return "BulkPhi"+_aliases; } - - @Override - public Type compute() { - Type t = super.compute(); - return t instanceof TypeMem mem ? mem : - t==Type.BOTTOM ? TypeMem.BOT : - TypeMem.TOP; - } - - @Override - public Node idealize() { - Node progress = super.idealize(); - if( progress != null ) return progress; - if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) - return null; // Input is in-progress - - // "Peek through" a MemMerge that covers this alias set on its default - for( int i=1; i= mmm.nIns() || mmm.in(alias)==null) ) - return false; - return true; - } - - - // True if the MemMerge has no precise slice still covered by this Phi. - private boolean canPeek(MemMergeNode mmm) { - return missingAlias(mmm,false)==0; - } - - // Return an alias required by an input but still covered by this Phi, or 0. - private int inputAlias(Node n) { - return switch(n) { - case ProjNode proj -> 0; - case ConstantNode con -> 0; - case MemMergeNode mmm -> missingAlias(mmm,false); - case MemOpNode mem -> unsplit(mem._alias); - case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw new AssertionError("Unexpected memory input "+n); - }; - } - - // Return an alias required by a user but still covered by this Phi, or 0. - private int outputAlias(Node use) { - // This decision depends backwards on an immediate user's alias/set. - // Normal graph-neighbor enqueueing flows from defs to uses, so retain - // an explicit forward dependency to revisit this Phi when the user - // sharpens. - use.addDepForwards(this); - return switch(use) { - case ScopeNode scope -> 0; - case MemMergeNode mmm -> missingAlias(mmm,true); - case BulkMemPhiNode bulk -> missingAlias(bulk); - case MemOpNode mem -> unsplit(mem._alias); - case MemPhiNode phi -> unsplit(phi._alias); - default -> { - assert use instanceof ReturnNode - : "Unexpected bulk-memory user "+use; - yield 0; - } - }; - } - - // Alias zero/one is bulk, and an already excluded precise alias cannot - // trigger another split of this Phi. - private int unsplit(int alias) { - if( alias <= 1 ) return 0; - assert !_aliases.get(alias); - return alias; - } - - // First alias excluded by the neighboring bulk Phi but not by this one. - private int missingAlias(BulkMemPhiNode bulk) { - for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( !_aliases.get(alias) ) - return alias; - return 0; - } - - // First precise MemMerge slice still covered by this Phi. When inspecting - // a user, ignore slots which point back to this Phi. - private int missingAlias(MemMergeNode mmm, boolean user) { - for( int alias=2; alias= 0; old = _aliases.nextSetBit(old+1) ) - { assert old != alias; mmm.alias(old,precisePhi(old));} - mmm.alias(alias,precise); - assert checkMerge(mmm); - return mmm; - } - - // Find or make the precise Phi parallel to this bulk Phi at the same merge. - private Node precisePhi(int alias) { - MemPhiNode mphi = _findPhi(alias); - if( mphi!=null ) return mphi; - - mphi = new MemPhiNode("$"+alias,alias); - mphi.addDef(region()); - // Due to cycles, must set before calling peephole - mphi.setType(TypeMem.make(alias)); - for( int i=1; i= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( alias >= mmm.nIns() || mmm.in(alias)==null ) - return false; - return true; - } - - @Override public boolean eq(Node n) { - return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); - } - - @Override int hash() { return _aliases.hashCode(); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index c1faf210..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.IterPeeps; - - -// Upcast (join) the input to a t. Used after guard test to lift an input. -// Can also be used to make a type-assertion if ctrl is null. -public class CastNode extends Node { - private final Type _t; - public CastNode(Type t, Node ctrl, Node in) { - super(ctrl, in); - _t = t; - setType(compute()); - } - - @Override public String label() { return "("+_t.str()+")"; } - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()).n(in(1)); - } - - @Override - public Type compute() { - return in(1)._type.join(_t); - } - - @Override - public Node idealize() { - return in(1)._type.isa(_t) ? in(1) : null; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index 426736ed..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - - -/** - * A Constant node represents a constant value. At present, the only constants - * that we allow are integer literals; therefore Constants contain an integer - * value. As we add other types of constants, we will refactor how we represent - * Constants. - *

- * Constants have no semantic inputs. However, we set Start as an input to - * Constants to enable a forward graph walk. This edge carries no semantic - * meaning, and it is present solely to allow visitation. - *

- * The Constant's value is the value stored in it. - */ -public class ConstantNode extends Node { - final Type _con; - public ConstantNode( Type type ) { - super(Parser.START); - _con = type; - } - - @Override protected String repeatName() { - return _con==null || _con.toString().length()<=32 ? null : uniqueName(); - } - - @Override - public String label() { return "Con"; } - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(_con); - } - - @Override public boolean isMultiTail() { return true; } - - @Override - public Type compute() { return _con; } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { - ConstantNode con = (ConstantNode)n; // Contract - return _con==con._con; - } - - @Override - int hash() { return _con.hashCode(); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index d7244ace..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Div"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return i1.value() == 0 - ? TypeInteger.ZERO - : TypeInteger.constant(i0.value()/i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // Div of 1. - if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 ) - return in(1); - return null; - } - - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index 45f695a5..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeTuple; - -public class IfNode extends MultiNode { - - public IfNode(Node ctrl, Node pred) { - super(ctrl, pred); - IterPeeps.add(this); // Because idoms are complex, just add it - } - - @Override - public String label() { return "If"; } - - @Override protected String format() { return "if( %1 )"; } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - public Node ctrl() { return in(0); } - public Node pred() { return in(1); } - - @Override - public Type compute() { - // If the If node is not reachable then neither is any following Proj - if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) - return TypeTuple.IF_NEITHER; - Node pred = pred(); - Type t = pred._type; - // High types mean NEITHER side is reachable. - // Wait until the type falls to decide which way to go. - if( t == Type.TOP || t == TypeInteger.TOP ) - return TypeTuple.IF_NEITHER; - // If constant is 0 then false branch is reachable - // Else true branch is reachable - if (t instanceof TypeInteger ti && ti.isConstant()) - return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; - - return TypeTuple.IF_BOTH; - } - - @Override - public Node idealize() { - // Hunt up the immediate dominator tree. If we find an identical if - // test on either the true or false branch, that side wins. - if( !pred()._type.isHighOrConst() ) - for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { - setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); - return this; - } - return null; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index 9bf36459..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.Field; - - -/** - * Load represents extracting a value from inside a memory object, - * in chapter 10 this means Struct fields. - */ -public class LoadNode extends MemOpNode { - - Type _declaredType; - /** - * Load a value from a ptr.field. - * - * @param name The field we are loading - * @param memSlice The memory alias node - this is updated after a Store - * @param memPtr The ptr to the struct from where we load a field - */ - public LoadNode(String name, int alias, Type glb, Node memSlice, Node memPtr) { - super(name, alias, memSlice, memPtr, null); - _declaredType = glb; - } - - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); - } - - // Check only immediate memory users. Stores clobber memory; Phis and - // aggregates might lead to a clobber, so stop rather than search further. - private boolean clobbered(Node dep) { - Node mem = mem(); - mem.addDepForwards(dep); - for( Node use : mem._outputs ) { - if( use==null ) continue; - use.addDepForwards(dep); - if( use instanceof StoreNode || use instanceof PhiNode || - use instanceof MemMergeNode ) return true; - } - return false; - } - - @Override - public String label() { return "Load"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { return p.p(".").p(_name); } - - @Override - public Type compute() { - return _declaredType; - } - - @Override - public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,IterPeeps.add(merge.alias(_alias))); - return this; - } - - - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - ptr() == st.ptr() && _alias==st._alias ) { // Must check same object - assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect - return st.val(); - } - - // Push a Load up through a Phi, as long as it collapses on at least - // one arm. If at a Loop, the backedge MUST collapse - else we risk - // spinning the same transform around the loop indefinitely. - // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): - // if( pred ) ptr.x = e0; val = pred ? e0 - // else ptr.x = e1; : e1; - // val = ptr.x; ptr.x = val; - if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { - // Profit on RHS/Loop backedge - if( profit(memphi,2) || - // Else must not be a loop to count profit on LHS. - (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - Node ld1 = new LoadNode(_name,_alias,_declaredType,memphi.in(1),ptr()).peephole(); - Node ld2 = new LoadNode(_name,_alias,_declaredType,memphi.in(2),ptr()).peephole(); - return new PhiNode(_name,_type,memphi.region(),ld1,ld2); - } - } - - return null; - } - - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _alias==st1._alias; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java deleted file mode 100644 index a0ba973e..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; - -import java.util.BitSet; - -public class LoopNode extends RegionNode { - - public LoopNode( Node entry ) { super(null,entry,null); } - - Node entry() { return in(1); } - Node back () { return in(2); } - - @Override - public String label() { return "Loop"; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - return entry()._type; - } - - @Override - public Node idealize() { - return inProgress() ? null : super.idealize(); - } - - // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) - @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } - @Override Node idom() { return entry(); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java deleted file mode 100644 index f3d5b01e..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; - -/** All memory: a default slice and explicit overrides for individual aliases. */ -public class MemMergeNode extends Node { - public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } - public MemMergeNode(Node bulk, int alias, Node precise) { - this(bulk); - alias(alias,precise); - } - - public Node alias(int alias) { - Node n = alias < nIns() ? in(alias) : null; - assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); - return n==null ? in(1) : n; - } - - public void alias(int alias, Node mem) { - assert alias > 1; - while( nIns()<=alias ) addDef(null); - setDef(alias,mem); - } - - @Override public String label() { return "MemMerge"; } - @Override public boolean isMem() { return true; } - @Override public Type compute() { return TypeMem.BOT; } - @Override protected ExprPrinter _print1(ExprPrinter p) { - p.p("MEM["); - for( int i=1; i=nIns() || in(i)==null) ) - alias(i,mem.in(i)); - IterPeeps.add(mem.in(1)); - setDef(1,mem.in(1)); - return this; - } - assert BulkMemPhiNode.checkMerge(this); - return progress ? this : null; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index 44304bcb..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Field; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - -/** - * Convenience common base for Load and Store. - */ -public abstract class MemOpNode extends Node { - - public final String _name; - public final int _alias; - - public MemOpNode(String name, int alias, Node memSlice, Node memPtr, Node value) { - super(null, memSlice, memPtr, value); - _name = name; - _alias = alias; - } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - - // Extra conditions for factoring matching memory operations through a Phi. - // The caller has already checked the opcode, input shape and control. - boolean canDrop(MemOpNode other, Node dep) { - ptr().addDep(dep); - return _alias==other._alias && err()==null; - } - - @Override - boolean eq(Node n) { - MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" - } - - @Override - int hash() { return _alias; } - - @Override - String err() { - Type ptr = ptr()._type; - return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) ) - ? "Might be null accessing '" + _name + "'" - : null; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java deleted file mode 100644 index c806bede..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; - -/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ -public class MemPhiNode extends PhiNode { - public final int _alias; - - public MemPhiNode(String label, int alias, Node... inputs) { - super(label,TypeMem.make(alias),inputs); - assert alias > 1; - _alias = alias; - } - - @Override public String label() { return "MemPhi_"+_alias; } - - @Override public Type compute() { - assert BulkMemPhiNode.checkMem(this,_alias); - Type t = super.compute(); - return t==Type.TOP || t==TypeMem.TOP ? TypeMem.TOP : TypeMem.make(_alias); - } - - @Override public Node idealize() { - for( int i=1; i _print1(ExprPrinter p) { - return p.p("new ").p(_ptr._obj.str()); - } - - @Override - public Type compute() { return _ptr; } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { return this == n; } - - @Override - int hash() { return _ptr.hashCode(); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index 1d8e23f8..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,604 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.BaseNode; - -import com.seaofnodes.simple.Parser; - -import com.seaofnodes.simple.IRPrinter; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; - -import java.util.*; -import java.util.function.Function; - -/** - * All Nodes in the Sea of Nodes IR inherit from the Node class. - * The Node class provides common functionality used by all subtypes. - * Subtypes of Node specialize by overriding methods. - */ -public abstract class Node extends BaseNode implements Cloneable { - - /** - * Inputs to the node. These are use-def references to Nodes. - *

- * Generally fixed length, ordered, nulls allowed, no unused trailing space. - * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a {@link #isCFG} node. - */ - public ArrayList _inputs; - - /** - * Outputs reference Nodes that are not null and have this Node as an - * input. These nodes are users of this node, thus these are def-use - * references to Nodes. - *

- * Outputs directly match inputs, making a directed graph that can be - * walked in either direction. These outputs are typically used for - * efficient optimizations but otherwise have no semantics meaning. - */ - public ArrayList _outputs; - - /** - * Current computed type for this Node. This value changes as the graph - * changes and more knowledge is gained about the program. - */ - public Type _type; - - /** - * A private Global Static mutable counter, for unique node id generation. - * To make the compiler multithreaded, this field will have to move into a TLS. - * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. - * */ - private static int UNIQUE_ID = 1; - public static int UID() { return UNIQUE_ID; } - - protected Node(Node... inputs) { - super(UNIQUE_ID++); // allocate unique dense ID - _inputs = new ArrayList<>(); - Collections.addAll(_inputs,inputs); - _outputs = new ArrayList<>(); - for( Node n : _inputs ) - if( n != null ) - n.addUse( this ); - } - - // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); - - public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - - public boolean isMultiHead() { return false; } - public boolean isMultiTail() { return false; } - - // ------------------------------------------------------------------------ - // Graph Node & Edge manipulation - - @Override public String typeName() { return _type==null ? null : _type.toString(); } - - /** - * Gets the ith input node - * @param i Offset of the input node - * @return Input node or null - */ - public Node in(int i) { return _inputs.get(i); } - public Node out(int i) { return _outputs.get(i); } - - public int nIns() { return _inputs.size(); } - - public int nOuts() { return _outputs.size(); } - - public boolean isUnused() { return nOuts() == 0; } - - public boolean isMem() { return false; } - - /** - * Change a def into a Node. Keeps the edges correct, by removing - * the corresponding use->def edge. This may make the original - * def go dead. This function is co-recursive with {@link #kill}. - *

- - * This method is the normal path for altering a Node, because it does the - * proper default edge maintenance. It also immediately kills - * Nodes that lose their last use; at times care must be taken to avoid - * killing Nodes that are being used without having an output Node. This - * definitely happens in the middle of recursive {@link #peephole} calls. - * - * @param idx which def to set - * @param new_def the new definition - * @return new_def for flow coding - */ - public Node setDef(int idx, Node new_def ) { - unlock(); - Node old_def = in(idx); - if( old_def == new_def ) return new_def; // No change - // If new def is not null, add the corresponding def->use edge - // This needs to happen before removing the old node's def->use edge as - // the new_def might get killed if the old node kills it recursively. - if( new_def != null ) - new_def.addUse(this); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); - moveDepsToWorklist(); - // Return self for easy flow-coding - return new_def; - } - - // Remove the numbered input, compressing the inputs in-place. This - // shuffles the order deterministically - which is suitable for Region and - // Phi, but not for every Node. If the def goes dead, it is recursively - // killed, which may include 'this' Node. - Node delDef(int idx) { - unlock(); - Node old_def = in(idx); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - old_def.moveDepsToWorklist(); - Utils.del(_inputs, idx); - return this; - } - - /** - * Add a new def to an existing Node. Keep the edges correct by - * adding the corresponding def->use edge. - * - * @param new_def the new definition, appended to the end of existing definitions - * @return new_def for flow coding - */ - Node addDef(Node new_def) { - unlock(); - // Add use->def edge - _inputs.add(new_def); - // If new def is not null, add the corresponding def->use edge - if( new_def != null ) - new_def.addUse(this); - return new_def; - } - - // Breaks the edge invariants, used temporarily - protected void addUse(N n) { _outputs.add(n); } - - // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. - // Return true if the output list is empty afterward. - // Error is 'use' does not exist; ok for 'use' to be null. - protected boolean delUse( Node use ) { - Utils.del(_outputs, Utils.find(_outputs, use)); - moveDepsToWorklist(); // User-count and anti-dependence queries can now change. - return _outputs.isEmpty(); - } - - // Shortcut for "popping" n nodes. A "pop" is basically a - // setDef(last,null) followed by lowering the nIns() count. - void popN(int n) { - unlock(); - for( int i=0; iuses, by setting all of its defs - * to null. This may recursively kill more Nodes, and is basically dead - * code elimination. - */ - public void kill( ) { - unlock(); - assert isUnused(); // Has no uses, so it is dead - _type=null; // Flag as dead - while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes - Node old_def = _inputs.removeLast(); - if( old_def != null ) { - IterPeeps.add(old_def);// Revisit neighbor because removed use - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - } - } - assert isDead(); // Really dead now - } - - // Mostly used for asserts and printing. - public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - - // Shortcuts to stop DCE mid-parse - // Add bogus null use to keep node alive - public N keep() { addUse(null); return (N)this; } - // Remove bogus null. - public N unkeep() { delUse(null); return (N)this; } - // Test "keep" status - public boolean iskeep() { return Utils.find(_outputs,null) != -1; } - - // Replace self with nnn in the graph, making 'this' go dead - public void subsume( Node nnn ) { - assert nnn!=this; - while( nOuts() > 0 ) { - Node n = _outputs.removeLast(); - n.unlock(); - int idx = Utils.find(n._inputs, this); - n._inputs.set(idx,nnn); - n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. - nnn.addUse(n); - } - kill(); - } - - // ------------------------------------------------------------------------ - // Graph-based optimizations - - /** - * Try to peephole at this node and return a better replacement Node. - * Always returns some not-null Node (often this). - */ - public final Node peephole() { - var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; - if( obs != null ) obs.before(this); - Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n.peephole()); - if( obs != null ) obs.after(this, n == null ? null : rez, false); - return rez; - } - - /** - * Try to peephole at this node and return a better replacement Node if - * possible. We compute a {@link Type} and then check and replace: - *

    - *
  • if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}
  • - *
  • in a future chapter we will look for a - * Common Subexpression - * to eliminate.
  • - *
  • we ask the Node for a better replacement. The "better replacement" - * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes - * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing - * math constants, remove algebraic identities and generally simplify the - * code.
  • - * - * Unlike peephole above, this explicitly returns null for no-change, or not-null - * for a better replacement (which can be this). - *
- */ - public final Node peepholeOpt( ) { - ITER_CNT++; - // Compute initial or improved Type - Type old = setType(compute()); - - // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && _type.isHighOrConst() ) - return new ConstantNode(_type).peepholeOpt(); - - // Global Value Numbering - if( _hash==0 ) { - Node n = GVN.get(this); // Will set _hash as a side effect - if( n==null ) - GVN.put(this,this); // Put in table now - else { - // Because of random worklist ordering, the two equal nodes - // might have different types. Because of monotonicity, both - // types are valid. To preserve monotonicity, the resulting - // shared Node has to have the best of both types. - n.setType(n._type.join(_type)); - _hash = 0; // Clear, since it never went in the table - return deadCodeElim(n);// Return previous; does Common Subexpression Elimination - } - } - - // Ask each node for a better replacement - Node n = idealize(); - if( n != null ) // Something changed - return n; // Report progress - - if( old==_type ) ITER_NOP_CNT++; - return old==_type ? null : this; // Report progress - } - public static int ITER_CNT, ITER_NOP_CNT; - - // m is the new Node, self is the old. - // Return 'm', which may have zero uses but is alive nonetheless. - // If self has zero uses (and is not 'm'), {@link #kill} self. - private Node deadCodeElim(Node m) { - // If self is going dead and not being returned here (Nodes returned - // from peephole commonly have no uses (yet)), then kill self. - if( m != this && isUnused() && !isDead() ) { - // Killing self - and since self recursively kills self's inputs we - // might end up killing 'm', which we are returning as a live Node. - // So we add a bogus extra null output edge to stop kill(). - m.keep(); // Keep m alive - kill(); // Kill self because replacing with 'm' - m.unkeep(); // Okay to peephole m - } - return m; - } - - /** - * This function needs to be - * Monotonic - * as it is part of a Monotone Analysis Framework. - * See for example this set of slides. - *

- * For Chapter 2, all our Types are really integer constants, and so all - * the needed properties are trivially true, and we can ignore the high - * theory. Much later on, this will become important and allow us to do - * many fancy complex optimizations trivially... because theory. - *

- * {@link #compute()} needs to be stand-alone, and cannot recursively call compute - * on its inputs, because programs are cyclic (have loops!) and this will just - * infinitely recurse until stack overflow. Instead, compute typically - * computes a new type from the {@link #_type} field of its inputs. - */ - public abstract Type compute(); - - // Set the type. Assert monotonic progress. - // If changing, add users to worklist. - public Type setType(Type type) { - Type old = _type; - assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger - if( old == type ) return old; - _type = type; // Set _type late for easier assert debugging - IterPeeps.addAll(_outputs); - moveDepsToWorklist(); - return old; - } - - /** - * This function rewrites the current Node into a more "idealized" form. - * This is the bulk of our peephole rewrite rules, and we use this to - * e.g. turn arbitrary collections of adds and multiplies with mixed - * constants into a normal form that's easy for hardware to implement. - * Example: An array addressing expression: - *

   ary[idx+1]
- * might turn into Sea-of-Nodes IR: - *
   (ary+12)+((idx+1) * 4)
- * This expression can then be idealized into: - *
   ary + ((idx*4) + (12 + (1*4)))
- * And more folding: - *
   ary + ((idx<<2) + 16)
- * And during code-gen: - *
   MOV4 Rary,Ridx,16 // or some such hardware-specific notation 
- *

- * {@link #idealize} has a very specific calling convention: - *

    - *
  • If NO change is made, return {@code null} - *
  • If ANY change is made, return not-null; this can be {@code this} - *
  • The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. - *
  • Any NEW nodes that are not directly returned DO call {@link #peephole}. - *
- *

- * Examples: - * - * - * - * - * - *
before after return comment
{@code (x+5) }{@code (x+5) }{@code null }No change
{@code (5+x) }{@code (x+5) }{@code this }Swapped arguments
{@code ((x+1)+2)}{@code (x+(1+2))}{@code (x+_) }Returns 2 new Nodes
- * - * The last entry deserves more discussion. The new Node {@code (1+2)} - * created in {@link #idealize} calls {@link #peephole} (which then folds - * into a constant). The other new Node {@code (x+3)} does not call - * peephole, because it is returned and peephole itself will recursively - * call peephole. - *

- * Since idealize calls peephole and peephole calls idealize, you must be - * careful that all idealizations are monotonic: all transforms - * remove some feature, so that the set of available transforms always - * shrinks. If you don't, you risk an infinite peephole loop! - * - * @return Either a new or changed node, or null for no changes. - */ - public abstract Node idealize(); - - // Some of the peephole rules get complex, and search further afield than - // just the nearest neighbor. These peepholes can fail the pattern match - // on a node some distance away, and if that node ever changes we should - // 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 - * or output of this node, that is, it is at least one step away. The node - * being added must benefit from this node being peepholed. - */ - Node addDep( Node dep ) { - return addDep(dep,false); - } - - // A bulk Phi inspects its users' partitions. Def-to-use propagation does - // not revisit that producer when an immediate user's partition changes. - Node addDepForwards(Node dep) { return addDep(dep,true); } - - private Node addDep(Node dep, boolean forwards) { - // 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( !forwards && Utils.find(_inputs,dep) != -1 ) return this; - if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; - _deps.add(dep); - return this; - } - - // Move the dependents onto a worklist, and clear for future dependents. - public void moveDepsToWorklist( ) { - if( _deps==null ) return; - IterPeeps.addAll(_deps); - _deps.clear(); - } - - // Global Value Numbering. Hash over opcode and inputs; hits in this table - // are structurally equal. - public static final HashMap GVN = new HashMap<>(); - - // Two nodes are equal if they have the same inputs and the same "opcode" - // which means the same Java class, plus same internal parts. - @Override public final boolean equals( Object o ) { - if( o==this ) return true; - if( o.getClass() != getClass() ) return false; - Node n = (Node)o; - int len = _inputs.size(); - if( len != n._inputs.size() ) return false; - for( int i=0; i>13) ^ n._nid; - if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing - return (_hash=hash); - } - // Subclasses add extra hash info (such as ConstantNodes constant) - int hash() { return 0; } - - // ------------------------------------------------------------------------ - // Peephole utilities - - // Swap inputs without letting either input go dead during the swap. - Node swap12() { - unlock(); // Hash is order dependent - Node tmp = in(1); - _inputs.set(1,in(2)); - _inputs.set(2,tmp); - return this; - } - - /** - * Does this node contain all constants? Ignores in(0), as is usually - * control. In an input is not a constant, we add dep as a dependency to - * it because dep can make progress if the input becomes a constant later. - * It is sufficient for one of the non-const inputs to have the dependency, - * so we don't bother checking the rest. - */ - boolean allCons(Node dep) { - for( int i=1; i - * See {@link ...} - */ - public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit - - // Find the lowest common ancestor in the current dominator tree. - Node domLCA(Node rhs) { - if( rhs==null ) return this; - Node lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = lhs.idom(); - if( comp <= 0 ) rhs = rhs.idom(); - } - return lhs; - } - - int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - return _idepth = (char)depth; - } - - // Return the immediate dominator of this Node. - Node idom() { return in(0); } - - // Make a shallow copy (same class) of this Node, with given inputs and - // empty outputs and a new Node ID. The original inputs are ignored. - // Does not need to be implemented in isCFG() nodes. - Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } - - // Exact-class copy preserving operation attributes, with fresh identity - // and no edges, dependencies, or GVN membership. - public final Node copyEmpty() { - Node n; - try { n = (Node)clone(); } - catch( CloneNotSupportedException e ) { throw new AssertionError(e); } - n._nid = UNIQUE_ID++; - n._inputs = new ArrayList<>(); - n._outputs = new ArrayList<>(); - n._deps = null; - n._hash = 0; - return n; - } - - // Report any post-optimize errors - String err() { return null; } - - /** - * Used to allow repeating tests in the same JVM. This just resets the - * Node unique id generator, and is done as part of making a new Parser. - */ - public static void reset() { - UNIQUE_ID = 1; - GVN.clear(); - ITER_CNT = ITER_NOP_CNT = 0; - } - - // Utility to walk the entire graph applying a function; return the first - // not-null result. - private static final BitSet WVISIT = new BitSet(); - final public E walk( Function pred ) { - assert WVISIT.isEmpty(); - E rez = _walk(pred); - WVISIT.clear(); - return rez; - } - - private E _walk( Function pred ) { - if( WVISIT.get(_nid) ) return null; // Been there, done that - WVISIT.set(_nid); - E x = pred.apply(this); - if( x != null ) return x; - for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; - for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; - return null; - } - - /** - * Debugging utility to find a Node by index - */ - public Node find(int nid) { - return walk( n -> n._nid==nid ? n : null ); - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index 5e556390..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.*; - -public class NotNode extends Node { - public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } - - @Override protected String format() { return "(!%1)"; } - - @Override - public Type compute() { - Type t0 = in(1)._type; - switch( t0 ) { - case TypeInteger i0: - return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; - case TypeMemPtr p0: - // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot - // If input in null then true - // If input is not null ptr then false - if( p0 == TypeMemPtr.TOP ) return TypeInteger.TOP; - if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.constant(1); - if( !p0._nil ) return TypeInteger.constant(0); - return TypeInteger.BOT; - case Type t: - if( t0.getClass() != Type.class ) - // Only doing NOT on ints and ptrs - throw Utils.TODO(); - return t0==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; - } - } - - @Override - public Node idealize() { return null; } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index 6995cc2f..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.Utils; - - -public class PhiNode extends Node { - - final String _label; - - // The Phi type we compute must stay within the domain of the Phi. - // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - final Type _declaredType; - - public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } - - public static PhiNode make(String label, Type type, Node... inputs) { - return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); - } - - @Override public String label() { return "Phi_"+_label; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - p.p("Z"); - p.p("Phi("); - for( Node in : _inputs ) p.n(in).p(","); - return p.unchar(',').close(); - } - - Node region() { return in(0); } - @Override public boolean isMultiTail() { return true; } - @Override - public boolean isMem() { return _declaredType instanceof TypeMem; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? Type.TOP : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() ) return _declaredType; - // Set type to local top of the starting type - Type t = _declaredType.glb().dual(); - for (int i = 1; i < nIns(); i++) - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - if( r.in(i).addDep(this)._type != Type.XCONTROL ) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( !(region() instanceof RegionNode r ) ) - return in(1); // Input has collapse to e.g. starting control. - if( r.inProgress() || r.nIns()<=1 ) - return null; // Input is in-progress - - // If we have only a single unique input, become it. - Node live = singleUniqueInput(); - if (live != null) - return live; - - // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). - Node down; - if( same_op() && (down=drop_same_op())!=null ) return down; - - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. - if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); - } - // If merging a null-checked null and the checked value, just use the value. - // if( val ) ..; phi(Region,False=0/null,True=val); - // then replace with plain val. - if( nIns()==3 ) { - int nullx = -1; - if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; - if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; - if( nullx != -1 ) { - Node val = in(3-nullx); - if( region().idom() instanceof IfNode iff && iff.pred().addDep(this)==val ) { - // Must walk the idom on the null side to make sure we hit False. - Node idom = region().in(nullx); - while( idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof ProjNode proj && proj._idx==1 ) - return val; - } - } - } - - return null; - } - - private boolean same_op() { - Node op = in(1); - if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || - op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; - // A bulk Phi must split its aliases before factoring precise Stores. - if( this instanceof BulkMemPhiNode ) return false; - // Bulk splitting currently identifies parallel slices by Region/alias. - // Factoring can introduce another slice at a different memory point; - // wait until that Region's bulk partitioning has finished. - if( op instanceof MemOpNode ) - for( Node use : region()._outputs ) - if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(this); - return false; - } - Node busy = null; - for( int i=1; i1 ) { - for( Node use : n._outputs ) - if( use!=null && use!=this ) use.addDepForwards(this); - if( n instanceof StoreNode || busy!=null ) return false; - busy=n; - } - for( int j=1; j _print1(ExprPrinter p) { return p.p(_label); } - - @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; } - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - - public MultiNode ctrl() { return (MultiNode)in(0); } - - @Override - public Type compute() { - Type t = in(0)._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if ( ctrl() instanceof IfNode iff ) { - if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) - return iff.in(0); // We become our input control - - // Flip a negating if-test, to remove the not - if( iff.pred().addDep(this) instanceof NotNode not ) - return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - } - return null; - } - - @Override - boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } - - @Override - int hash() { return _idx; } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 8b52494a..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; - -import java.util.ArrayList; -import java.util.Arrays; - -public class RegionNode extends Node { - - public RegionNode(Node... nodes) { super(nodes); } - - @Override - public String label() { return "Region"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()).p(_nid); - } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( inProgress() ) return null; - // Delete dead paths into a Region - int path = findDeadInput(); - if( path != 0 && - // Do not delete the entry path of a loop (ok to remove the back - // edge and make the loop a single-entry Region which folds away - // the Loop). Folding the entry path confused the loop structure, - // moving the backedge to the entry point. - !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { - // Cannot use the obvious output iterator here, because a Phi - // deleting an input might recursively delete *itself*. This - // shuffles the output array, and we might miss iterating an - // unrelated Phi. So on rare occasions we repeat the loop to get - // all the Phis. - int nouts = 0; - while( nouts != nOuts() ) { - nouts = nOuts(); - for( int i=0; i1 && in(nIns()-1) == null; - } - - // Never equal if inProgress - @Override boolean eq( Node n ) { - return !inProgress(); - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index ef4ddb3a..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - - -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ -public class ReturnNode extends Node { - - public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - super(ctrl, scope.lookup("$mem"), data); - } - - public Node ctrl() { return in(0); } - public Node mem () { return in(1); } - public Node expr() { return in(2); } - - @Override - public String label() { return "Return"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("return "); - p.n(expr()); - return p.p(";"); - } - - @Override public boolean isCFG() { return true; } - - @Override - public Type compute() { - // Return exposes the complete memory state. - return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); - } - - @Override - public Node idealize() { - if( ctrl()._type==Type.XCONTROL ) - return ctrl(); - return null; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index b408eb2f..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.TypeMemPtr; - -import java.util.*; - -/** - * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of scopes. - */ -public class ScopeNode extends Node { - - /** - * The control is a name that binds to the currently active control - * node in the graph - */ - public static final String CTRL = "$ctrl"; - public static final String ARG0 = "arg"; - - /** - * Names for every input edge - */ - public final Stack> _scopes; - - /** - * Tracks declared types for every name - */ - public Stack> _types; - - - // A new ScopeNode - public ScopeNode() { - _scopes = new Stack<>(); - _types = new Stack<>(); - _type = Type.BOTTOM; - } - - - @Override public String label() { return "Scope"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("Scope[ "); - String[] names = reverseNames(); - for( int j=0; j syms : _scopes ) - for( String name : syms.keySet() ) - names[syms.get(name)] = name; - return names; - } - - @Override public Type compute() { return Type.BOTTOM; } - - @Override public Node idealize() { return null; } - - public void push() { _scopes.push(new HashMap<>()); _types.push(new HashMap<>()); } - public void pop() { popN(_scopes.pop().size()); _types.pop(); } - - /** - * Create a new name in the current scope - */ - public Node define( String name, Type declaredType, Node n ) { - HashMap syms = _scopes.lastElement(); - _types.lastElement().put(name,declaredType); - if( syms.put(name,nIns()) != null ) - return null; // Double define - return addDef(n); - } - /** - * Lookup a name in all scopes starting from most deeply nested. - * - * @param name Name to be looked up - * @see #update(String, Node, int) - */ - public Node lookup( String name ) { return update(name,null,_scopes.size()-1); } - /** - * If the name is present in any scope, then redefine else null - * - * @param name Name being redefined - * @param n The node to bind to the name - */ - public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); } - /** - * Both recursive lookup and update. - *

- * A shared implementation allows us to create lazy phis both during - * lookups and updates; the lazy phi creation is part of chapter 8. - * - * @param name Name whose binding is being updated or looked up - * @param n If null, do a lookup, else update binding - * @param nestingLevel The starting nesting level - * @return node being looked up, or the one that was updated - */ - private Node update( String name, Node n, int nestingLevel ) { - if( nestingLevel<0 ) return null; // Missed in all scopes, not found - var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level - var idx = syms.get(name); - if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope - Node old = in(idx); - if( old instanceof ScopeNode loop ) { - // Lazy Phi! - old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? loop.in(idx) - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,PhiNode.make(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); - setDef(idx,old); - } - return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer - } - - // Return declared type - public Type lookupDeclaredType( String name ) { - for( int i=_types.size(); i>0; i-- ) { - Type t = _types.get(i-1).get(name); - if( t != null ) return t; - } - return null; - } - - public Node ctrl() { return in(0); } - - /** - * The ctrl of a ScopeNode is always bound to the currently active - * control node in the graph, via a special name '$ctrl' that is not - * a valid identifier in the language grammar and hence cannot be - * referenced in Simple code. - * - * @param n The node to be bound to '$ctrl' - * - * @return Node that was bound - */ - public Node ctrl(Node n) { return setDef(0,n); } - - /** - * Duplicate a ScopeNode; including all levels, up to Nodes. So this is - * neither shallow (would dup the Scope but not the internal HashMap - * tables), nor deep (would dup the Scope, the HashMap tables, but then - * also the program Nodes). - *

- * If the {@code loop} flag is set, the edges are filled in as the original - * Scope, as a indication of Lazy Phis at loop heads. The goal here is to - * not make Phis at loop heads for variables which are never touched in the - * loop body. - *

- * The new Scope is a full-fledged Node with proper use<->def edges. - */ - public ScopeNode dup() { return dup(false); } - public ScopeNode dup(boolean loop) { - ScopeNode dup = new ScopeNode(); - // Our goals are: - // 1) duplicate the name bindings of the ScopeNode across all stack levels - // 2) Make the new ScopeNode a user of all the nodes bound - // 3) Ensure that the order of defs is the same to allow easy merging - for( HashMap syms : _scopes ) - dup._scopes.push(new HashMap<>(syms)); - for( HashMap ts : _types ) - dup._types.push(ts); // Types don't change, just keep stacks aligned - - dup.addDef(ctrl()); // Control input is just copied - for( int i=1; i _print1(ExprPrinter p) { - return p.p(label()); - } - - @Override public boolean isCFG() { return true; } - @Override public boolean isMultiHead() { return true; } - - @Override - public TypeTuple compute() { return _args; } - - @Override - public Node idealize() { return null; } - - // No immediate dominator, and idepth==0 - @Override int idepth() { return 0; } - @Override Node idom() { return null; } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index 4b4e9615..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.IterPeeps; - - -public class StopNode extends Node { - - public final String _src; - - public StopNode(String src) { - super(); - _src = src; - } - - @Override - public String label() { - return "Stop"; - } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( ret()!=null ) return p.n(ret()); - p.p("Stop[ "); - for( Node ret : _inputs ) - p.n(ret).p(" "); - return p.p("]"); - } - - @Override public boolean isCFG() { return true; } - - // If a single Return, return it. - // Otherwise, null because ambiguous. - public ReturnNode ret() { - return nIns()==1 ? (ReturnNode)in(0) : null; - } - - @Override - public Type compute() { - return Type.BOTTOM; - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i { - String msg = n.err(); - if( msg != null && obs != null ) obs.error(n, msg); - return msg; - }); - if( err != null ) throw new RuntimeException(err); - if( obs != null ) obs.phase("TypeCheck"); - return this; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java deleted file mode 100644 index 026d5c50..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.*; - - -/** - * Store represents setting a value to a memory based object, in chapter 10 - * this means a field inside a struct. - */ -public class StoreNode extends MemOpNode { - - /** - * @param name The struct field we are assigning to - * @param memSlice The memory alias node - this is updated after a Store - * @param memPtr The ptr to the struct where we will store a value - * @param value Value to be stored - */ - public StoreNode(String name, int alias, Node memSlice, Node memPtr, Node value) { - super(name, alias, memSlice, memPtr, value); - } - - @Override - public String label() { return "Store"; } - @Override - public boolean isMem() { return true; } - public Node val() { return in(3); } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - return p.p(".").p(_name).p("=").p( val()).p(";"); - } - - @Override - public Type compute() { return TypeMem.make(_alias); } - - @Override - public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,IterPeeps.add(merge.alias(_alias))); - return this; - } - - - // Simple store-after-store on same address. Should pick up the - // required init-store being stomped by a first user store. - if( mem() instanceof StoreNode st && - ptr()==st.ptr() && _alias==st._alias && // Must check same object - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - st.checkNoUseBeyond(this) ) { - assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect - setDef(1,st.mem()); - return this; - } - - return null; - } - - // Check that `this` has no uses beyond `that` - private boolean checkNoUseBeyond(Node that) { - if( nOuts()==1 ) return true; - // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that - // when the other uses go away we can retry. - for( Node use : _outputs ) - if( use != that ) - use.addDep(that); - return false; - } - -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java deleted file mode 100644 index 6fa87a07..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class SubNode extends Node { - public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Sub"; } - - @Override protected String format() { return "(%1-%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1==Type.TOP || t1==TypeInteger.TOP || - t2==Type.TOP || t2==TypeInteger.TOP ) - return TypeInteger.TOP; - // Sub of same is 0 - if( in(1)==in(2) ) - return TypeInteger.ZERO; - - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(i0.value()-i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Keep invalid operands for type checking. - if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { - if( y.isConstant() && y.value()==0 ) return lhs; - if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); - } - - // x - (-y) is x+y - if( rhs instanceof MinusNode minus ) - return new AddNode(lhs,minus.in(1)); - - // (-x) - y is -(x+y) - if( lhs instanceof MinusNode minus ) - return new MinusNode(new AddNode(minus.in(1),rhs).peephole()); - - return null; - } - - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java deleted file mode 100644 index 92d970d8..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphAdapter; -import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMem; - -/** Chapter 10's view of the IR; browser, transport and layout are shared. */ -public class SimpleGraphAdapter extends GraphAdapter { - - @Override protected String[] edgeNames(Node n) { - return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; - } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : -1; } - - @Override protected Kind kind(Node n) { - if( n instanceof StopNode ) return Kind.STOP; - 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; - } - - @Override protected 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/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java deleted file mode 100644 index 1980659e..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ /dev/null @@ -1,45 +0,0 @@ -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); - _phase = "TypeCheck"; - _parser.STOP.typeCheck(); - } - @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 Node scope() { return _parser._scope; } - @Override protected void roots(ArrayList roots) { - roots.add(Parser.START); - roots.add(_parser.STOP); - roots.addAll(_parser._xScopes); - roots.add(_ret); - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index cdc58215..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a field in a struct. This is not a Type in the type system. - */ -public class Field extends Type { - - // The pair {fieldName,type} uniquely identifies a field. - - // Field name - public final String _fname; - // Type of the field - public final Type _type; - - private Field(String fname, Type type ) { - super(TFLD); - _fname = fname; - _type = type; - } - public static Field make( String fname, Type type ) { - return new Field(fname,type).intern(); - } - - public static final Field TEST = make("test",TypeInteger.ZERO); - public static void gather(ArrayList ts) { ts.add(TEST); } - - @Override Field xmeet( Type that ) { - Field fld = (Field)that; // Invariant - assert _fname.equals(fld._fname); - return make(_fname,_type.meet(fld._type)); - } - - @Override - public Field dual() { return make(_fname,_type.dual()); } - - @Override - public Field glb() { - return make(_fname,_type.glb()); - } - - // Override in subclasses - int hash() { return _fname.hashCode() ^ _type.hashCode(); } - - boolean eq(Type t) { - Field f = (Field)t; - return Utils.eq(_fname,f._fname) && _type==f._type; - } - - - @Override - public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_fname).append(":")); - } - - @Override public String str() { return _fname; } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index 93e27c76..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * These types are part of a Monotone Analysis Framework, - * @see see for example this set of slides. - *

- * The types form a lattice; @see a symmetric complete bounded (ranked) lattice. - *

- * This wild lattice theory will be needed later to allow us to easily beef up - * the analysis and optimization of the Simple compiler... but we don't need it - * now, just know that it is coming along in a later Chapter. - *

g - * One of the fun things here is that while the theory is deep and subtle, the - * actual implementation is darn near trivial and is generally really obvious - * what we're doing with it. Right now, it's just simple integer math to do - * simple constant folding e.g. 1+2 == 3 stuff. - */ - -public class Type { - static final HashMap INTERN = new HashMap<>(); - - // ---------------------------------------------------------- - // Simple types are implemented fully here. "Simple" means: the code and - // type hierarchy are simple, not that the Type is conceptually simple. - static final byte TBOT = 0; // Bottom (ALL) - static final byte TTOP = 1; // Top (ANY) - static final byte TCTRL = 2; // Ctrl flow bottom - static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) - static final byte TSIMPLE = 4; // End of the Simple Types - static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TMEMPTR = 8; // Memory pointer type - static final byte TSTRUCT = 9; // Structs; tuples with named fields - static final byte TFLD =10; // Fields into struct - - public final byte _type; - - public boolean is_simple() { return _type < TSIMPLE; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; - protected Type(byte type) { _type = type; } - - public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL - public static final Type TOP = new Type( TTOP ).intern(); // ANY - public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl - public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl - public static Type[] gather() { - ArrayList ts = new ArrayList<>(); - ts.add(BOTTOM); - ts.add(CONTROL); - Field.gather(ts); - TypeInteger.gather(ts); - TypeMem.gather(ts); - TypeMemPtr.gather(ts); - TypeStruct.gather(ts); - TypeTuple.gather(ts); - int sz = ts.size(); - for( int i = 0; i < sz; i++ ) - ts.add(ts.get(i).dual()); - return ts.toArray(new Type[ts.size()]); - } - - // Is high or on the lattice centerline. - public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } - - // Strict constant values, things on the lattice centerline. - // Excludes both high and low values - public boolean isConstant() { return false; } - - /** - * Display Type name in a format that's good for IR printer - */ - public StringBuilder typeName( StringBuilder sb) { return print(sb); } - - public Type makeInit() { return null; } - - // ---------------------------------------------------------- - - // Notes on Type interning. - // At the moment it is not easy to reset the interned types - // because we hold static references to several types and these are scattered - // around. This means the INTERN cache will retain all types from - // every run of the Parser. For this to work correctly types must be - // rigorous about defining when they are the same. Also types need to be - // immutable once defined. - // The rationale for interning is performance. - - // Factory method which interns "this" - public T intern() { - T nnn = (T)INTERN.get(this); - if( nnn==null ) - INTERN.put(nnn=(T)this,this); - return nnn; - } - - private int _hash; // Hash cache; not-zero when set. - @Override - public final int hashCode() { - if( _hash!=0 ) return _hash; - _hash = hash(); - if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing - return _hash; - } - // Override in subclasses - int hash() { return _type; } - - @Override - public final boolean equals( Object o ) { - if( o==this ) return true; - if( !(o instanceof Type t)) return false; - if( _type != t._type ) return false; - return eq(t); - } - // Overridden in subclasses; subclass can assume "this!=t" and java classes are same - boolean eq(Type t) { return true; } - - - // ---------------------------------------------------------- - public final Type meet(Type t) { - // Shortcut for the self case - if( t == this ) return this; - // Same-type is always safe in the subclasses - if( _type==t._type ) return xmeet(t); - // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". - if( is_simple() ) return this.xmeet(t ); - if( t.is_simple() ) return t .xmeet(this); - return Type.BOTTOM; // Mixing 2 unrelated types - } - - // Compute meet right now. Overridden in subclasses. - // Handle cases where 'this.is_simple()' and unequal to 't'. - // Subclassed xmeet calls can assert that '!t.is_simple()'. - Type xmeet(Type t) { - assert is_simple(); // Should be overridden in subclass - // ANY meet anything is thing; thing meet ALL is ALL - if( _type==TBOT || t._type==TTOP ) return this; - if( _type==TTOP || t._type==TBOT ) return t; - // 'this' is {TCTRL,TXCTRL} - if( !t.is_simple() ) return BOTTOM; - // 't' is {TCTRL,TXCTRL} - return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; - } - - public Type dual() { - return switch( _type ) { - case TBOT -> TOP; - case TTOP -> BOTTOM; - case TCTRL -> XCONTROL; - case TXCTRL -> CONTROL; - default -> throw Utils.TODO(); // Should not reach here - }; - } - - // ---------------------------------------------------------- - // Our lattice is defined with a MEET and a DUAL. - // JOIN is dual of meet of both duals. - public final Type join(Type t) { - if( this==t ) return this; - return dual().meet(t.dual()).dual(); - } - - // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } - - /** - * Compute greatest lower bound in the lattice - */ - public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; } - - // ---------------------------------------------------------- - // Useful in the debugger, which calls toString everywhere. - // This is a more verbose dev-friendly print. - @Override - public final String toString() { - return print(new StringBuilder()).toString(); - } - - public StringBuilder print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} - - // This is used by error messages, and is a shorted print. - public String str() { return STRS[_type]; } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index 8c77672b..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Integer Type - */ -public class TypeInteger extends Type { - - public final static TypeInteger TOP = make(false, 0); - public final static TypeInteger BOT = make(false, 1); - public final static TypeInteger ZERO= make(true , 0); - - public final boolean _is_con; - - /** - * The constant value or - * if not constant then 1=bottom, 0=top. - */ - public final long _con; - - private TypeInteger(boolean is_con, long con) { - super(TINT); - _is_con = is_con; - _con = con; - } - public static TypeInteger make(boolean is_con, long con) { - return new TypeInteger(is_con,con).intern(); - } - - public static TypeInteger constant(long con) { return make(true, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append(_con); - } - - @Override public String str() { - if( this==TOP ) return "~int"; - if( this==BOT ) return "int"; - return ""+_con; - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append("Int"); - } - - @Override - public boolean isHighOrConst() { return _is_con || _con==0; } - - @Override - public boolean isConstant() { return _is_con; } - - public long value() { return _con; } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeInteger) - TypeInteger i = (TypeInteger)other; // Contract - // BOT wins - if ( this==BOT ) return this; - if ( i ==BOT ) return i ; - // TOP loses - if ( i ==TOP ) return this; - if ( this==TOP ) return i ; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return BOT; - } - - @Override - public Type dual() { - if( isConstant() ) return this; // Constants are a self-dual - return _con==0 ? BOT : TOP; - } - - @Override - public Type glb() { return BOT; } - - @Override public TypeInteger makeInit() { return ZERO; } - - @Override - int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); } - @Override - public boolean eq( Type t ) { - TypeInteger i = (TypeInteger)t; // Contract - return _con==i._con && _is_con==i._is_con; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index b6fc8824..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.lang.Long; -import java.util.BitSet; -import java.util.ArrayList; - - -/** - * Represents a slice of memory corresponding to a set of aliases - */ -public class TypeMem extends Type { - - // Which slice of memory? - // 0 means TOP, no slice. - // -1 means BOT, all memory. - // N means slice#N. - private final int _alias; - - private TypeMem(int alias) { super(TMEM); _alias = alias; } - - public static TypeMem make(int alias) { return new TypeMem(alias).intern(); } - public static final TypeMem TOP = make( 0); - public static final TypeMem BOT = make(-1); - - - public static void gather(ArrayList ts) { ts.add(make(1)); ts.add(BOT); } - - @Override - TypeMem xmeet(Type t) { - TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal - return _alias==0 ? that : (that._alias==0 ? this : BOT); - } - - @Override - public Type dual() { - if( _alias== 0 ) return BOT; - if( _alias==-1 ) return TOP; - return this; - } - - @Override - public Type glb() { return TypeMem.BOT; } - - @Override - int hash() { return 9876543 + _alias; } - - @Override - boolean eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias; - } - - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("MemBot"); - if( this==TOP ) return sb.append("MemTop"); - return sb.append("MEM#").append( switch(_alias) { - case 0 -> "TOP"; - case -1 -> "BOT"; - default -> ""+_alias; - }); - } - - @Override public String str() { return print(new StringBuilder()).toString(); } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 6c874a93..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Represents a Pointer to memory. - * - * Null is generic pointer to non-existent memory. - * *void is a non-Null pointer to all possible structs. - * Pointers can be to specific struct types, or a union with Null. - * The distinguished *$BOT ptr represents union of *void and Null. - * The distinguished *$TOP ptr represents the dual of *$BOT. - */ -public class TypeMemPtr extends Type { - // A TypeMemPtr is pair (obj,nil) - // where obj is one of - // (null,TypeStruct). - // where nil is one of - // (true,false) meaning an explicit null is allowed or not - - public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY - public final boolean _nil; - - private TypeMemPtr(TypeStruct obj, boolean nil) { - super(TMEMPTR); - _obj = obj; - _nil = nil; - } - public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } - public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); } - - public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); - public static final TypeMemPtr TOP = BOT.dual(); - public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); - public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null - public static final TypeMemPtr TEST= make(TypeStruct.TEST,false); - public static void gather(ArrayList ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); } - - @Override - Type xmeet(Type t) { - TypeMemPtr that = (TypeMemPtr) t; - return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil); - } - - @Override - public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); } - - @Override - public Type glb() { - if( _obj==null ) return BOT; - return make(_obj.glb(),true); - } - @Override public TypeMemPtr makeInit() { return NULLPTR; } - - @Override - int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); } - - @Override - boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _obj == ptr._obj && _nil == ptr._nil; - } - - // [void,name,MANY]*[,?] - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("PtrBot"); - if( this==TOP ) return sb.append("PtrTop"); - if( this== NULLPTR) return sb.append("null"); - if( this== VOIDPTR) return sb.append("*void"); - return _obj.print(sb.append("*")).append(_nil ? "?" : ""); - } - - @Override public String str() { - if( this==BOT ) return "PtrBot"; - if( this==TOP ) return "PtrTop"; - if( this== NULLPTR) return "null"; - if( this== VOIDPTR) return "*void"; - return "*"+_obj.str()+(_nil ? "?" : ""); - } - -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 44741def..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a struct type. - */ -public class TypeStruct extends Type { - - // For now in terms of the lattice - // a struct type just stays as is - - public final String _name; - public final Field[] _fields; - - private TypeStruct(String name, Field[] fields) { - super(Type.TSTRUCT); - _name = name; - _fields = fields; - } - - public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); } - public static TypeStruct make(String name, ArrayList fields) { return make(name,fields.toArray(new Field[fields.size()])); } - - public static final TypeStruct TOP = make("$TOP",new Field [0]); - public static final TypeStruct BOT = make("$BOT",new Field [0]); - public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); - public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); } - - public int find(String fname) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._fname.equals(fname) ) - return i; - return -1; - } - - @Override - Type xmeet(Type t) { - TypeStruct that = (TypeStruct) t; - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - - // Within the same compilation unit, struct names are unique. If the - // names differ, its different structs. Across many compilation units, - // structs with the same name but different field layouts can be - // interned... which begs the question: - // "What is the meet of structs from two different compilation units?" - // And the answer is: "don't ask". - if( !_name.equals(that._name) ) - return BOT; // It's a struct; that's about all we know - // Now all fields should be the same, so just do field meets - assert _fields.length == that._fields.length; - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = (Field)_fields[i].meet(that._fields[i]); - return make(_name,flds); - } - - @Override - public TypeStruct dual() { - if( this==TOP ) return BOT; - if( this==BOT ) return TOP; - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].dual(); - return make(_name,flds); - } - - // Keeps the same struct, but lower-bounds all fields. - @Override - public TypeStruct glb() { - if( _glb() ) return this; - // Need to glb each field - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].glb(); - return make(_name,flds); - } - private boolean _glb() { - for( Field f : _fields ) - if( f.glb() != f ) - return false; - return true; - } - - @Override - boolean eq(Type t) { - TypeStruct ts = (TypeStruct)t; // Invariant - if( !_name.equals(ts._name) ) - return false; - if( _fields.length!=ts._fields.length ) - return false; - for( int i = 0; i < _fields.length; i++ ) - if( _fields[i] != ts._fields[i] ) - return false; - return true; - } - - @Override - int hash() { - long hash = _name.hashCode(); - for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode(); - return Utils.fold(hash); - } - - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("StructBot"); - if( this==TOP ) return sb.append("StructTop"); - sb.append(_name).append(" {\n"); - for( Field f : _fields ) { - sb.append(" ").append(f._fname).append(":"); - f._type.print(sb); - sb.append(";\n"); - } - return sb.append("}"); - } - - @Override public String str() { - if( this==BOT ) return "StructBot"; - if( this==TOP ) return "StructTop"; - return _name; - } -} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java deleted file mode 100644 index 0aa019a7..00000000 --- a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -public class TypeTuple extends Type { - - public final Type[] _types; - - private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } - public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } - - private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static void gather(ArrayList ts) { ts.add(TEST); } - - @Override - Type xmeet(Type other) { - TypeTuple tt = (TypeTuple)other; // contract from xmeet - assert _types.length == tt._types.length; - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].meet(tt._types[i]); - return make(ts); - } - - @Override public Type dual() { - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].dual(); - return make(ts); - } - - @Override - public Type glb() { - Type[] ts = new Type[_types.length]; - for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i].glb(); - return make(ts); - } - - @Override - public StringBuilder print(StringBuilder sb) { - sb.append("["); - for( Type t : _types ) - t.print(sb).append(","); - sb.setLength(sb.length()-1); - sb.append("]"); - return sb; - } - - @Override public String str() { return print(new StringBuilder()).toString(); } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - sb.append("["); - for( Type t : _types ) - t.typeName(sb).append(","); - sb.setLength(sb.length()-1); - sb.append("]"); - return sb; - } - - public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); - public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); - public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); - public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); - - @Override - int hash() { - int sum = 0; - for( Type type : _types ) sum ^= type.hashCode(); - return sum; - } - - @Override - boolean eq( Type t ) { - TypeTuple tt = (TypeTuple)t; // Contract - if( _types.length != tt._types.length ) return false; - for( int i=0; i<_types.length; i++ ) - if( _types[i]!=tt._types[i] ) - return false; - return true; - } - - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java deleted file mode 100644 index 89882b2f..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter01Test { - - @Test - public void testSimpleProgram() { - Parser parser = new Parser("return 1;"); - StopNode stop = parser.parse(); - StartNode start = Parser.START; - ReturnNode ret = (ReturnNode)stop.in(0); - - assertTrue(ret.ctrl() instanceof ProjNode); - Node expr = ret.expr(); - if( expr instanceof ConstantNode con ) { - assertEquals(start,con.in(0)); - assertEquals(TypeInteger.constant(1), con._type); - } else { - fail(); - } - } - - @Test - public void testZero() { - Parser parser = new Parser("return 0;"); - parser.parse(); - StartNode start = Parser.START; - for( Node use : start._outputs ) - if( use instanceof ConstantNode con && con._type instanceof TypeInteger ) - assertEquals(TypeInteger.constant(0),con._type); - } - - @Test - public void testBad1() { - try { - new Parser("ret").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'ret'",e.getMessage()); - } - } - - @Test - public void testBad2() { - try { - new Parser("return 0123;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); - } - } - - @Test - public void testNotBad3() { - // this test used to fail in chapter 1 - assertEquals("return 12;", new Parser("return --12;").parse().print()); - } - - @Test - public void testBad4() { - try { - new Parser("return 100").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected ;: ",e.getMessage()); - } - } - - @Test - public void testNotBad5() { - // this test used to fail in chapter 1 - assertEquals("return -100;", new Parser("return -100;").parse().print()); - } - - @Test - public void testBad6() { - try { - new Parser("return100").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'return100'",e.getMessage()); - } - } - - @Test - public void testBad7() { - try { - new Parser("return 1;}").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, unexpected }",e.getMessage()); - } - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java deleted file mode 100644 index a6075b75..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter02Test { - - @Test - public void testParseGrammar() { - Parser parser = new Parser("return 1+2*3+-5;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - - @Test - public void testAddPeephole() { - Parser parser = new Parser("return 1+2;"); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.print()); - } - - @Test - public void testSubPeephole() { - Parser parser = new Parser("return 1-2;"); - StopNode ret = parser.parse(); - assertEquals("return -1;", ret.print()); - } - - @Test - public void testMulPeephole() { - Parser parser = new Parser("return 2*3;"); - StopNode ret = parser.parse(); - assertEquals("return 6;", ret.print()); - } - - @Test - public void testDivPeephole() { - Parser parser = new Parser("return 6/3;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - - @Test - public void testMinusPeephole() { - Parser parser = new Parser("return 6/-3;"); - StopNode ret = parser.parse(); - assertEquals("return -2;", ret.print()); - } - - @Test - public void testExample() { - Parser parser = new Parser("return 1+2*3+-5;"); - StopNode ret = parser.parse(); - assertEquals("return 2;", ret.print()); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java deleted file mode 100644 index 609dff76..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter03Test { - - @Test - public void testVarDecl() { - Parser parser = new Parser("int a=1; return a;"); - StopNode ret = parser.parse(); - assertEquals("return 1;", ret.print()); - } - - @Test - public void testVarAdd() { - Parser parser = new Parser("int a=1; int b=2; return a+b;"); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.print()); - } - - @Test - public void testVarScope() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;"); - StopNode ret = parser.parse(); - assertEquals("return 4;", ret.print()); - } - - @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); "); - StopNode ret = parser.parse(); - assertEquals("return 8;", ret.print()); - } - - @Test - public void testSelfAssign() { - try { - new Parser("int a=a; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'a'",e.getMessage()); - } - } - - @Test - public void testRedeclareVar() { - try { - new Parser("int a=1; int b=2; int c=0; int b=3; c=a+b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Redefining name 'b'",e.getMessage()); - } - } - - @Test - public void testBad1() { - try { - new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected }: ",e.getMessage()); - } - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java deleted file mode 100644 index edc62f50..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter04Test { - - @Test - public void testPeephole() { - Parser parser = new Parser("return 1+arg+2; "); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testPeephole2() { - Parser parser = new Parser("return (1+arg)+2;"); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testAdd0() { - Parser parser = new Parser("return 0+arg;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void testAddAddMul() { - Parser parser = new Parser("return arg+0+arg;"); - StopNode ret = parser.parse(); - assertEquals("return (arg*2);", ret.print()); - } - - @Test - public void testPeephole3() { - Parser parser = new Parser("return 1+arg+2+arg+3; "); - StopNode ret = parser.parse(); - assertEquals("return ((arg*2)+6);", ret.print()); - } - - @Test - public void testMul1() { - Parser parser = new Parser("return 1*arg;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void testSub0() { - Parser parser = new Parser("return arg-0;"); - StopNode ret = parser.parse(); - assertEquals("return arg;", ret.print()); - } - - @Test - public void test0Sub() { - Parser parser = new Parser("return 0-arg;"); - StopNode ret = parser.parse(); - assertEquals("return (-arg);", ret.print()); - } - - @Test - public void testSubNeg() { - Parser parser = new Parser("return 3--arg;"); - StopNode ret = parser.parse(); - assertEquals("return (arg+3);", ret.print()); - } - - @Test - public void testNegSub() { - Parser parser = new Parser("return -arg-3;"); - StopNode ret = parser.parse(); - assertEquals("return (-(arg+3));", ret.print()); - } - - @Test - public void testVarArg() { - Parser parser = new Parser("return arg; "); - StopNode stop = parser.parse(); - ReturnNode ret = stop.ret(); - assertTrue(ret.in(0) instanceof ProjNode); - assertTrue(ret.in(1) instanceof ProjNode); - assertEquals(1, ((ProjNode)ret.in(1))._idx); - assertTrue(ret.in(2) instanceof ProjNode); - assertEquals(2, ((ProjNode)ret.in(2))._idx); - } - - @Test - public void testConstantArg() { - 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; "); - StopNode ret = parser.parse(); - assertEquals("return 1;", ret.print()); - } - - @Test - public void testCompEq2() { - 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; "); - StopNode ret = parser.parse(); - assertEquals("return 0;", ret.print()); - } - - @Test - public void testCompNEq2() { - 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; "); - 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; "); - StopNode ret = parser.parse(); - assertEquals("return (arg+1);", ret.print()); - } - - @Test - public void testBug3() { - try { - new Parser("inta=1; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'inta'",e.getMessage()); - } - } - - @Test - public void testBug4() { - Parser parser = new Parser("return -arg;"); - StopNode ret = parser.parse(); - assertEquals("return (-arg);", ret.print()); - } - - @Test - public void testBug5() { - Parser parser = new Parser("return arg--2;"); - StopNode ret = parser.parse(); - assertEquals("return (arg--2);", ret.print()); - } -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java deleted file mode 100644 index cf7bd6cf..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter05Test { - - @Test - public void testIfStmt() { - Parser parser = new Parser( -""" -int a = 1; -if (arg == 1) - a = arg+2; -else { - a = arg-3; - -} - -return a;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); - } - - @Test - public void testTest() { - Parser parser = new Parser( -""" -int c = 3; -int b = 2; -if (arg == 1) { - b = 3; - c = 4; -} -return c;""", TypeInteger.BOT); - StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region17,4,3);", ret.toString()); - } - - @Test - public void testReturn2() { - Parser parser = new Parser( -""" -if( arg==1 ) - return 3; -else - return 4; -""", TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("Stop[ return 3; return 4; ]", stop.toString()); - } - - @Test - public void testIfMergeB() { - Parser parser = new Parser( -""" -int a=arg+1; -int b=0; -if( arg==1 ) - b=a; -else - b=a+1; -return a+b;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); - } - - @Test - public void testIfMerge2() { - Parser parser = new Parser( -""" -int a=arg+1; -int b=arg+2; -if( arg==1 ) - b=b+a; -else - a=b+1; -return a+b;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); - } - - @Test - public void testMerge3() { - Parser parser = new Parser( -""" -int a=1; -if( arg==1 ) - if( arg==2 ) - a=2; - else - a=3; -else if( arg==3 ) - a=4; -else - a=5; -return a; -""", TypeInteger.BOT); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); - } - - @Test - public void testMerge4() { - Parser parser = new Parser( -""" -int a=0; -int b=0; -if( arg ) - a=1; -if( arg==0 ) - b=2; -return arg+a+b; -""", TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,2,0));", stop.toString()); - } - - @Test - public void testMerge5() { - Parser parser = new Parser( -""" -int a=arg==2; -if( arg==1 ) -{ - a=arg==3; -} -return a;"""); - StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region17,3,2));", ret.toString()); - } - - @Test - public void testMergeSameCon() { - Parser parser = new Parser( -""" -int a=arg; -if( arg==1 ) - a=3; -else - a=3; -return a; -"""); - StopNode ret = parser.parse(); - assertEquals("return 3;", ret.toString()); - } - - @Test - public void testTrue() { - StopNode stop = new Parser("return true;").parse(); - assertEquals("return 1;",stop.toString()); - } - - @Test - public void testHalfDef() { - try { - new Parser("if( arg==1 ) int b=2; return b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); - } - } - - @Test - public void testHalfDef2() { - try { - new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'b'",e.getMessage()); - } - } - - @Test - public void testRegress1() { - try { - new Parser("if(arg==2) int a=1; else int b=2; return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); - } - } - - - @Test - public void testBadNum() { - try { - new Parser("return 1-;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); - } - } - - @Test - public void testKeyword1() { - try { - new Parser("int true=0; return true;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Expected an identifier, found 'true'",e.getMessage()); - } - } - - @Test - public void testKeyword2() { - try { - new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Expected an identifier, found 'else'",e.getMessage()); - } - } - - @Test - public void testKeyword3() { - try { - new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("Undefined name 'ififif'",e.getMessage()); - } - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java deleted file mode 100644 index 920e26e3..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ /dev/null @@ -1,294 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Chapter06Test { - - @Test - public void testPeepholeReturn() { - Parser parser = new Parser( -""" -if( true ) return 2; -return 1; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof ProjNode); - } - - @Test - public void testPeepholeRotate() { - Parser parser = new Parser( -""" -int a = 1; -if (arg) - a = 2; -return (arg < a) < 3; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return ((arg 4 ) - break; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testRegress2() { - Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testBreakOutsideLoop() { - try { - new Parser(""" -if(arg <= 10) { - break; - arg = arg + 1; -} -return arg; -""").parse(); - fail(); - } catch( RuntimeException e ) { - assertEquals("No active loop for a break or continue",e.getMessage()); - } - } - - @Test - public void testRegress3() { - Parser parser = new Parser(""" -while(arg < 10) { - break; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testRegress4() { - Parser parser = new Parser(""" -int a = 1; -while(arg < 10) { - a = a + 1; - if (arg > 2) { - int a = 17; - break; - } -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop8,1,(Phi_a+1)),Add);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof RegionNode); - } - - @Test - public void testRegress5() { - Parser parser = new Parser(""" -int a = 1; -while(1) { - a = a + 1; - if (a<10) continue; - break; -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop8,1,Add)+1);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java deleted file mode 100644 index 1b1920e8..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.Ignore; - -import static org.junit.Assert.*; - -public class Chapter09Test { - - @Test - @Ignore - public void testJig() { - Parser parser = new Parser(""" -int v0=0; -arg=0; -while(v0) { - while(1) if(arg*arg*0==0) {} - while(0) {} - arg=1; -} -return 0; - """); - StopNode stop = parser.parse().iterate(); - } - - @Test - public void testGVN1() { - Parser parser = new Parser( - """ -int x = arg + arg; -if(arg < 10) { - return arg + arg; -} -else { - x = x + 1; -} -return x; - """); - StopNode stop = parser.parse(); - assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); - Assert.assertEquals(2L, Evaluator.evaluate(stop, 1)); - Assert.assertEquals(23L, Evaluator.evaluate(stop, 11)); - } - - @Test - public void testGVN2() { - Parser parser = new Parser( - """ -return arg*arg-arg*arg; - """); - StopNode stop = parser.parse(); - assertEquals("return 0;", stop.toString()); - Assert.assertEquals(0L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist1() { - Parser parser = new Parser( - """ -int step = 1; -while (arg < 10) { - arg = arg + step + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); - Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist2() { - Parser parser = new Parser( - """ -int cond = 0; -int one = 1; -while (arg < 10) { - if (cond) one = 2; - arg = arg + one*3 + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); - Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testWorklist3() { - Parser parser = new Parser( - """ -int v1 = 0; -int v2 = 0; -int v3 = 0; -int v4 = 0; -int v5 = 0; -int v6 = 0; -int v7 = 0; -int v8 = 0; -while (arg) { - if (v1) v2 = 1; - if (v2) v3 = 1; - if (v3) v4 = 1; - if (v4) v5 = 1; - if (v5) v6 = 1; - if (v6) v7 = 1; - if (v7) v8 = 1; - arg = arg + v8 + 1; -} -return arg; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,(Phi_arg+1));", stop.toString()); - } - - @Test - public void testRegionPeepBug() { - Parser parser = new Parser( - """ -int v0=0; -int v1=0; -while(v1+arg) { - arg=0; - int v2=v0; - while(arg+1) {} - v0=1; - v1=v2; -} - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testWhile0() { - Parser parser = new Parser("while(0) continue; if(0) arg=0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testWhile1() { - Parser parser = new Parser(""" -if(0) while(0) { - int arg=arg; - while(0) {} -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - - @Test - public void testPrecedence() { - Parser parser = new Parser("return 3-1+2;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 4;", stop.toString()); - } - - @Test - public void testSwap2() { - Parser parser = new Parser("return 1+(1+1);"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 3;", stop.toString()); - } - - - @Test - public void testFuzz0() { - Parser parser = new Parser(""" -int one = 1; -int a = 0; -int zero = 0; -while(arg) { - a = -(one + a + 2); - arg = arg + 1; - one = one + zero; -} -return a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,(-(Phi_a+3)));", stop.toString()); - } - - @Test - public void testFuzz1() { - Parser parser = new Parser(""" -while(1) {} -while(arg) break; -while(arg) arg=0; -arg=0; -int v0=0!=0<-0; -return -0+0+0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - - @Test - public void testFuzz2() { - Parser parser = new Parser("return 0+-0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testFuzz3() { - Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz4() { - Parser parser = new Parser(""" -while(1) { - arg=0<=0; - if(1<0) while(arg==-0) arg=arg-arg; -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz5() { - Parser parser = new Parser(""" -{ - int v0=0; - while(1) - int v1=0--0; - while(v0) - break; - while(-v0) { - while(0+0+v0) continue; - break; - } - if(-0!=-0+0+v0) while(0+0+0+0) - break; -} -return 0!=0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz6() { - Parser parser = new Parser(""" -int v0=0; -while(0==1) while(v0) - v0=1+v0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz7() { - Parser parser = new Parser(""" -while(1) {} -int v0=0; -while(v0) - {} -int v1=0; -while(1) - v1=1; -return v1+v0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testFuzz8() { - Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,arg,(Phi_arg-1));", stop.toString()); - } - - @Test - public void testMeet() { - Type t1 = Type.TOP; - Type t2 = TypeInteger.TOP; - Assert.assertEquals(TypeInteger.TOP, t1.meet(t2)); - Assert.assertEquals(TypeInteger.TOP, t2.meet(t1)); - t1 = Type.BOTTOM; - t2 = TypeInteger.BOT; - Assert.assertEquals(Type.BOTTOM, t1.meet(t2)); - Assert.assertEquals(Type.BOTTOM, t2.meet(t1)); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java deleted file mode 100644 index 8bf1debe..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ /dev/null @@ -1,583 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.node.LoadNode; -import com.seaofnodes.simple.node.StoreNode; -import com.seaofnodes.simple.evaluator.Evaluator; -import java.util.BitSet; -import org.junit.Test; - -import java.util.Arrays; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class Chapter10Test { - @Test public void testDeadReferenceReturn() { - for( String body : new String[] { - "return new S; return 0;", - "if(1) return new S; else return 0;" - } ) { - String src = "struct S { int x; }; " + body; - var stop = new Parser(src).parse().iterate(); - org.junit.Assert.assertTrue(body, - stop.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); - } - } - - - - // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. - private static final String NULLABLE_POINT_SOURCE = """ - struct Point { int x; }; - Point point = new Point; - point.x = 42; - Point? p = null; - if (arg) p = point; - """; - - @Test - public void testNullGuards() { - for( String body : new String[] { - "if (p != null) return p.x; return -1;", - "if (null != p) return p.x; return -1;", - "if (!!!!p) return p.x; return -1;", - "if (!!!p) return -1; else return p.x;", - "int b = !!p; if (b) return p.x + b - 1; return -1;" - } ) { - StopNode stop = new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); - assertEquals(body,-1L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); - assertEquals(body,42L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); - } - } - - @Test - public void testNullGuardErrors() { - for( String body : new String[] { - "return p.x;", - "if (p == null) return p.x; return -1;", - "if (!!point) return p.x; return -1;", - "if (!!p) { int x = p.x; } return p.x;" - } ) { - try { - new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); - fail(body); - } catch( RuntimeException e ) { - // Known null is rejected during parsing in these chapters. - String expected = body.startsWith("if (p == null)") - ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'"; - assertEquals(body,expected,e.getMessage()); - } - } - } - - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -int a = arg/3; -int b = arg*5; -int x = arg*7; -int y = arg/11; -int p; int g; int h; -if( (arg/13)==0 ) { - p = x + y; - g = x; - h = y; -} else { - p = a + b; - g = a; - h = b; -} -int r = g+h; -return p-r; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testStruct() { - Parser parser = new Parser(""" -struct Bar { - int a; - int b; -} -struct Foo { - int x; -} -Foo? foo = null; -Bar bar = new Bar; -bar.a = 1; -bar.a = 2; -return bar.a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - } - - @Test - public void testExample() { - Parser parser = new Parser(""" -struct Vector2D { int x; int y; } -Vector2D v = new Vector2D; -v.x = 1; -if (arg) - v.y = 2; -else - v.y = 3; -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new Vector2D;", stop.toString()); - } - - @Test - public void testBug() { - Parser parser = new Parser(""" -struct s0 { - int v0; -} -s0? v1=null; -int v3=v1.zAicm; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); } - } - - @Test - public void testBug2() { - Parser parser = new Parser(""" -struct s0 { int v0; } -arg=0+new s0.0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } - } - - @Test - public void testLoop() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = new Bar; -while (arg) { - bar.a = bar.a + 2; - arg = arg + 1; -} -return bar.a; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString()); - } - - @Test - public void testIf() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = new Bar; -if (arg) bar = null; -bar.a = 1; -return bar.a; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); } - } - - @Test - public void testIf2() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar? bar = null; -if (arg) bar = new Bar; -bar.a = 1; -return bar.a; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); } - } - - @Test - public void testIf3() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar bar = null; -if (arg) bar = null; -bar.a = 1; -return bar.a; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); } - } - - @Test - public void testIfOrNull() { - Parser parser = new Parser(""" -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -if( bar ) bar.a = 1; -return bar; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region16,null,new Bar);", stop.toString()); - } - - @Test - public void testIfOrNull2() { - Parser parser = new Parser( -""" -struct Bar { int a; } -Bar? bar = new Bar; -if (arg) bar = null; -int rez = 3; -if( !bar ) rez=4; -else bar.a = 1; -return rez; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,4,3);", stop.toString()); - } - - @Test - public void testWhileWithNullInside() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0? v0 = new s0; -int ret = 0; -while(arg) { - ret = v0.v0; - v0 = null; - arg = arg - 1; -} -return ret; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); } - } - - @Test - public void testRedeclareStruct() { - Parser parser = new Parser(""" -struct s0 { - int v0; -} -s0? v1=new s0; -s0? v1; -v1=new s0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); } - } - - @Test - public void testIter() { - // Build and use an iterator - Parser parser = new Parser( -""" -struct Iter { - int x; - int len; -} -Iter i = new Iter; -i.len = arg; -int sum=0; -while( i.x < i.len ) { - sum = sum + i.x; - i.x = i.x + 1; -} -return sum; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); - } - - - - - @Test - public void test1() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -while(arg) { - s0 v0 = new s0; - v0.v0 = arg; - arg = arg-1; - if (arg==5) ret=v0; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); - } - - @Test - public void test2() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -s0 v0 = new s0; -while(arg) { - v0.v0 = arg; - arg = arg-1; - if (arg==5) ret=v0; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); - } - - - @Test - public void test3() { - Parser parser = new Parser(""" -struct s0 {int v0;} -s0 ret = new s0; -while(arg < 10) { - s0 v0 = new s0; - if (arg == 5) ret=v0; - arg = arg + 1; -} -return ret; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); - } - - @Test - public void testBug3() { - Parser parser = new Parser(""" -struct s0 { - int f0; -} -if(0>=0) return new s0; -return new s0; -int v0=null.f0; -"""); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); } - } - - @Test - public void testBug4() { - Parser parser = new Parser(""" -if(0) { - while(0) if(arg) continue; - int v0=0; - while(1) { - int arg=-arg; - v0=arg; - } -} - """); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ ]", stop.toString()); - } - - @Test - public void testBug5() { - Parser parser = new Parser(""" -struct s0 { - int f0; -} -if(0) return 0; -else return new s0; -if(new s0.f0) return 0; - """); - StopNode stop = parser.parse().iterate(); - assertEquals("return new s0;", stop.toString()); - } - - @Test - public void testBug6MissedWorklist() { - Parser parser = new Parser(""" -while(0) {} -int v4=0; -while(0 0) { - s.x = s.x + 1; - int j = 3; - while (j > 0) { - j = j - 1; - if (j == 1) continue; - s.y = s.y + arg; - if (arg == 2) break; - s.z = s.z + 1; - } - arg = arg - 1; - } - return s; - """; - - @Test public void testMemoryAcrossNestedLoops() { - var stop = new Parser(NESTED_MEMORY).parse().iterate(); - for (int arg=0; arg<7; arg++) { - var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) - com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); - org.junit.Assert.assertArrayEquals(new Object[] { - 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0) - }, obj.fields()); - } - } - - @Test public void testMemoryAtEarlyReturns() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S s = new S; - s.x = 3; s.y = 5; s.z = 7; - if (arg) { s.x = 11; s.z = 13; return s; } - s.y = 17; - return s; - """).parse().iterate(); - for (int arg=0; arg<2; arg++) { - var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) - com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); - org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields()); - } - } - - @Test public void testKeepLoadsAtMerge() { - StopNode stop = new Parser(""" - struct S { int x; } - S a = new S; S b = new S; - a.x = arg; b.x = arg+1; - S p = a; S q = b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) v=p.x; else v=q.x; - return v; - """).parse().iterate(); - // The one-step safety check stops at the memory aggregate. - assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals(-1L,Evaluator.evaluate(stop,-1)); - assertEquals(1L,Evaluator.evaluate(stop,0)); - assertEquals(3L,Evaluator.evaluate(stop,3)); - } - - @Test public void testDropStores() { - StopNode stop = new Parser(""" - struct S { int x; } - S s = new S; - if (arg) s.x=arg+1; else s.x=arg+2; - return s; - """).parse().iterate(); - // The initializing slice also remains live as the aggregate default. - assertEquals(2,countMemoryNodes(stop,StoreNode.class,new BitSet())); - assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); - assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); - } - - @Test public void testKeepReadsBeforeWrites() { - StopNode stop = new Parser(""" - struct S { int x; int y; } - S a = new S; S b = new S; - a.x=arg; b.x=arg+1; - S p=a; S q=b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) { v=p.x; p.x=41; p.y=5; } - else { v=q.x; q.x=42; } - return v; - """).parse().iterate(); - assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals(-1L,Evaluator.evaluate(stop,-1)); - assertEquals(1L,Evaluator.evaluate(stop,0)); - assertEquals(3L,Evaluator.evaluate(stop,3)); - } - - private static int countMemoryNodes(Node n, Class kind, BitSet seen) { - if (n==null || seen.get(n._nid)) return 0; - seen.set(n._nid); - int cnt=kind.isInstance(n) ? 1 : 0; - for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); - return cnt; - } -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 6f094cfb..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; } - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java deleted file mode 100644 index 019140ad..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.fuzzer.Fuzzer; -import org.junit.Test; -import org.junit.Ignore; - - -import static org.junit.Assert.assertTrue; - -/** - * Normal tests run chapter-local regression seeds. Open failures and exploratory - * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore). - * Seeds depend on this chapter's generator; do not copy them between chapters. - */ -public class FuzzerWrap { - - private static final long[] REGRESSION_SEEDS = { - }; - - private static final long[] OPEN_FAILING_SEEDS = { - }; - - @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); } - @Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); } - - private static void fuzzPeepsSeeds(long... seeds) { - var fuzzer = new Fuzzer(); - for (long seed : seeds) - fuzzer.fuzzPeepsRegression(seed); - } - - @Test - @Ignore - public void fuzzPeepsLarge() { - var fuzzer = new Fuzzer(); - for (int i=0; i<1000000; i++) - fuzzer.fuzzPeeps(i); - assertTrue(fuzzer.noExceptions()); - } - - - @Test - @Ignore - public void fuzzPeepTiming() { - var fuzzer = new Fuzzer(); - int max_nid=0; - for (int i=0; i<1000000; i++) - max_nid = fuzzer.fuzzPeepTiming(i, max_nid); - } -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java deleted file mode 100644 index ad595822..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SchedulerTest { - - private static void assertObj(Object obj, String name, Object... fields) { - assertEquals(Evaluator.Obj.class, obj.getClass()); - var o = (Evaluator.Obj)obj; - assertEquals(name, o.struct()._name); - Assert.assertArrayEquals(fields, o.fields()); - } - - @Test - @Ignore - public void testJig() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v=new S; -v.f = 2; -int i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse();//.iterate(); - var eval = new Evaluator(stop); - assertEquals(2L, eval.evaluate(1, 10)); - } - - @Test - public void testStoreInIf() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v0=new S; -if(arg) v0.f=1; -return v0; -"""); - StopNode stop = parser.parse().iterate(); - var eval = new Evaluator(stop); - assertObj(eval.evaluate(0, 10), "S", 0L); - assertObj(eval.evaluate(1, 10), "S", 1L); - } - - @Test - public void testStoreInIf2() { - Parser parser = new Parser(""" -struct S { - int f; -} -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - 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/chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java deleted file mode 100644 index f9c580cc..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.type.*; -import org.junit.Assert; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class TypeTest { - - // Test basic properties and GLB - @Test - public void testTypeAdHoc() { - TypeStruct s1 = TypeStruct.make("s1", new Field[]{ - Field.make("a", TypeInteger.BOT), - Field.make("b", TypeInteger.BOT) }); - TypeStruct s2 = TypeStruct.make("s2", new Field[]{ - Field.make("a", TypeInteger.BOT), - Field.make("b", TypeInteger.BOT) }); - Assert.assertEquals(s1, s1.glb()); - Assert.assertNotEquals(s1, s1.dual()); - Assert.assertEquals(s1, s1.dual().glb()); - - TypeMem m1 = TypeMem.make(1); - TypeMem m2 = TypeMem.make(2); - TypeMem m3 = TypeMem.make(3); - TypeMem m4 = TypeMem.make(4); - - Assert.assertNotEquals(m1, m2); - Assert.assertNotEquals(m2, m3); - Assert.assertNotEquals(m3, m4); - - Assert.assertEquals(TypeStruct.BOT, s1.meet(s2)); - Assert.assertEquals(TypeMem .BOT, m1.meet(m2)); - Assert.assertEquals(TypeMem .BOT, m2.meet(m3)); - Assert.assertEquals(TypeMem .BOT, m3.meet(m4)); - - Assert.assertEquals(TypeMem.BOT, m1.glb()); - Assert.assertEquals(m1, m1.dual()); - Assert.assertEquals(TypeMem.TOP, m1.glb().dual()); - - TypeMemPtr ptr1 = TypeMemPtr.make(s1); - Assert.assertEquals(s1, ptr1._obj); - TypeMemPtr ptr2 = TypeMemPtr.make(s2); - Assert.assertEquals(s2, ptr2._obj); - - TypeMemPtr ptr1nil = TypeMemPtr.make(s1, true); - Assert.assertEquals(s1, ptr1nil._obj); - Assert.assertTrue(ptr1nil._nil); - Assert.assertFalse(ptr1nil._obj==null); - TypeMemPtr ptr2nil = TypeMemPtr.make(s2, true); - Assert.assertEquals(s2, ptr2nil._obj); - - Assert.assertNotEquals(ptr1, ptr2); - Assert.assertNotEquals(ptr1, ptr1.glb()); - Assert.assertEquals(ptr1nil, ptr1.glb()); - - Assert.assertEquals(ptr1, ptr1.dual().dual()); - Assert.assertEquals(ptr1.glb(), ptr1.dual().glb()); - Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil)); - Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR)); - - TypeMemPtr TOP = TypeMemPtr.TOP; - TypeMemPtr BOT = TypeMemPtr.BOT; - TypeMemPtr PTR = TypeMemPtr.VOIDPTR; - TypeMemPtr NULL = TypeMemPtr.NULLPTR; - Type PTR_meet_NULL = NULL.meet(PTR); - Assert.assertEquals(BOT, PTR_meet_NULL); - Type ptr1_meet_ptr2 = ptr1.meet(ptr2); - Assert.assertEquals(PTR, ptr1_meet_ptr2); - Type NULL_join_ptr1 = NULL.join(ptr1); - Assert.assertEquals(TOP, NULL_join_ptr1); - Type NULL_join_PTR = PTR.join(NULL); - Assert.assertEquals(TOP, NULL_join_PTR); - Type ptr1_dual = ptr1.dual(); - Type nullableptr1_dual = ptr1nil.dual(); - } - - // Test theoretical properties. - // This is a symmetric complete bounded (ranked) lattice. - // Also the meet is commutative and associative. - // The lattice has a dual (symmetric), and join is ~(~x meet ~y). - // See https://en.wikipedia.org/wiki/Lattice_(order). - @Test - public void testLatticeTheory() { - Type[] ts = Type.gather(); - - // Confirm commutative & complete - for( Type t0 : ts ) - for( Type t1 : ts ) { - check_commute (t0,t1); - check_symmetric(t0,t1); - } - - // Confirm associative - for( Type t0 : ts ) - for( Type t1 : ts ) - for( Type t2 : ts ) - assoc(t0,t1,t2); - - // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C) - for( Type t0 : ts ) - for( Type t1 : ts ) - if( t0.isa(t1) ) - for( Type t2 : ts ) { - Type t02 = t0.join(t2); - Type t12 = t1.join(t2); - Type mt = t02.meet(t12); - assertSame(mt,t12); - } - } - - // By design in meet, args are already flipped to order _type, which forces - // symmetry for things with badly ordered _type fields. The question is - // still interesting for other orders. - private static void check_commute( Type t0, Type t1 ) { - if( t0==t1 ) return; - if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order - Type mta = t0.meet(t1); - Type mtb = t1.meet(t0); // Reverse args and try again - assertSame(mta,mtb); - } - - // A & B = MT - // Expect: ~A & ~MT == ~A - // Expect: ~B & ~MT == ~B - private static void check_symmetric( Type t0, Type t1 ) { - if( t1==t0 ) return; - Type mt = t0.meet(t1); - Type ta = mt.dual().meet(t1.dual()); - Type tb = mt.dual().meet(t0.dual()); - assertSame(ta,t1.dual()); - assertSame(tb,t0.dual()); - } - - private static void assoc( Type t0, Type t1, Type t2 ) { - Type t01 = t0 .meet(t1 ); - Type t12 = t1 .meet(t2 ); - Type t01_2 = t01.meet(t2 ); - Type t0_12 = t0 .meet(t12); - assertSame(t01_2,t0_12); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java deleted file mode 100644 index ac7c5e09..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.seaofnodes.simple.evaluator; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Field; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.type.TypeStruct; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Objects; - -public class Evaluator { - - private static final Object MEMORY = new Object(); - - public record Obj(TypeStruct struct, Object[] fields) { - @Override - public String toString() { - var sb = new StringBuilder(); - sb.append("Obj<").append(struct._name).append("> {"); - for(int i=0; i= 0 ) return idx; - throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name); - } - - public enum Status { - TIMEOUT, - FALLTHROUGH - } - - /** - * Find the start node from some node in the graph or null if there is no start node - */ - private static StartNode findStart(BitSet visit, Node node) { - if (node == null) return null; - if( visit.get(node._nid) ) return null; - visit.set(node._nid); - StartNode start = node instanceof StartNode ? (StartNode) node : null; - for( Node def : node._inputs ) { - var res = findStart(visit,def); - if( res != null ) start = res; - } - for( Node use : node._outputs ) { - var res = findStart(visit,use); - if( res != null ) start = res; - } - return start; - } - - private final Object[] values; - private final ArrayList phiCache = new ArrayList<>(); - private final StartNode start; - private final Scheduler.Block startBlock; - - public Evaluator(Node graph) { - var visited = new BitSet(); - this.start = findStart(visited, graph); - if (this.start == null) { - this.values = null; - this.startBlock = null; - return; - } - this.startBlock = Scheduler.schedule(this.start); - this.values = new Object[visited.length()]; - } - - private long div(DivNode div) { - long in2 = vall(div.in(2)); - return in2 == 0 ? 0 : vall(div.in(1)) / in2; - } - - private Object alloc(NewNode alloc) { - var type = ((TypeMemPtr)alloc.compute())._obj; - return new Obj(type, new Object[type._fields.length]); - } - - private Object load(LoadNode load) { - var from = (Obj)val(load.in(2)); - var idx = getFieldIndex(from.struct, load); - return from.fields[idx]; - } - - private Object store(StoreNode store) { - var to = (Obj)val(store.in(2)); - var val = val(store.in(3)); - var idx = getFieldIndex(to.struct, store); - to.fields[idx] = val; - return null; - } - - private static boolean isTrue(Object obj) { - if (obj == null) return false; - if (obj instanceof Number n) return n.longValue() != 0; - return true; - } - - private Object val(Node node) { - return values[node._nid]; - } - - private long vall(Node node) { - var v = val(node); - if (v instanceof Number n) return n.longValue(); - throw new AssertionError("Not a long " + v); - } - - private Object cons(ConstantNode cons) { - var type = cons.compute(); - if (type instanceof TypeInteger i) return i.value(); - assert type instanceof TypeMemPtr; - return null; - } - - private Object exec(Node node) { - return switch (node) { - case ConstantNode cons -> cons(cons); - case AddNode add -> vall(add.in(1)) + vall(add.in(2)); - case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; - case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; - case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; - case DivNode div -> div(div); - case MinusNode minus -> -vall(minus.in(1)); - case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); - case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; - case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); - case CastNode cast -> val(cast.in(1)); - case LoadNode load -> load(load); - case StoreNode store -> store(store); - case MemMergeNode merge -> MEMORY; - case NewNode alloc -> alloc(alloc); - case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; - default -> throw new AssertionError("Unexpected node " + node); - }; - } - - /** - * Run the graph until either a return is found or the number of loop iterations are done. - */ - public Object evaluate(long parameter, int loops) { - if (start == null) return Status.TIMEOUT; - var s = new Object[start.compute()._types.length]; - s[1] = MEMORY; - s[2] = parameter; - values[start._nid] = s; - int i=0; - Scheduler.Block block = this.startBlock; - for(;;) { - for(; i 0 && region.nIns() > exit; - block = block.next()[0]; - assert block != null; - for (; i < block.nodes().length; i++) { - if (!(block.nodes()[i] instanceof PhiNode)) break; - phiCache.add(val(block.nodes()[i].in(exit))); - } - for (i=0; i - *
- * This scheduler is used for tests and is not the final scheduler. - *
- *
- * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. - * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy - * this scheduler might place initialization code in loops. - *
- * We schedule late here since memory edges have some implicit information. There can only be one - * memory edge (for a field) at a time. However, before if statements this might split. - * When scheduling early a store might be moved out of the loop but then there are two edges alive. - * The one through the store node and the adjacent one. This is an invalid schedule and instead of - * handling this case a late scheduler does not have this problem as memory edges are combined - * through phi nodes. - *
- *
- * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. - * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes - * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. - */ -public class Scheduler { - - /** - * A basic block with a schedule of the containing nodes. - * - * @param nodes The nodes in the final schedule of this block - * @param exit The exit node of this block. - * @param exitId In case the next block is a region this gives the entry id into the region. - * @param next The next blocks. - */ - public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} - - /** - * Definition of a block in the building process. - */ - private static class BasicBlock { - - /** - * Dominator of this block - */ - final BasicBlock dom; - /** - * Id of this block where a.depth < b.depth means that a is before b or - * both are in different branches. - */ - final int depth; - /** - * Previous blocks - */ - final BasicBlock[] prev; - /** - * Entry to this block - */ - final Node entry; - /** - * Schedules node in reverse order. - */ - final ArrayList reverseSchedule = new ArrayList<>(); - - /** - * Initialize the block. - * @param entry The node that started this block - * @param depth The depth of this node. All previous nodes need to have a lower value. - * @param prev The previous blocks. - */ - BasicBlock(Node entry, int depth, BasicBlock... prev) { - BasicBlock dom = null; - int max = -1; - for(var bb: prev) { - max = Math.max(max, bb.depth); - dom = dom==null?bb:dom(dom, bb); - } - assert depth > max; - this.dom = dom; - this.depth = depth; - this.entry = entry; - this.prev = prev; - } - - } - - /** - * Ancillary data for nodes used during the scheduling process. - */ - private static class NodeData { - - /** - * The node this ancillary data is used for. - */ - final Node node; - - /** - * Number of alive users not yet scheduled. - */ - int users = 1; - - /** - * Block into which the node should be placed so far. - * This will be updated with every placed use. - */ - BasicBlock block; - - /** - * Initialize ancillary data for a node - * @param node The node this ancillary data is associated with. - */ - NodeData(Node node) { - this.node = node; - } - - } - - /** - * Ancillary data for nodes. - */ - private final IdentityHashMap data = new IdentityHashMap<>(); - /** - * List of nodes which can be placed since all users are placed. - */ - private final Stack scheduleQueue = new Stack<>(); - - /** - * Get the ancillary data for an alive node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node. - */ - NodeData d(Node node) { - return od(node).orElseThrow(AssertionError::new); - } - - /** - * Get the ancillary data for a potentially dead node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node an alive node or - * Optional.empty() for null or dead nodes. - */ - Optional od(Node node) { - return Optional.ofNullable(data.get(node)); - } - - /** - * Checks a node for validity - * @param data The node to check. - * @return true if all placed inputs are before this node. - */ - private boolean isValid(NodeData data) { - return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d); - } - - /** - * Checks that node is not XCtrl. - * @param node The node to check. - * @return true if the node is not XCtrl. - */ - private static boolean isNotXCtrl(Node node) { - return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); - } - - /** - * Return the dominator of a and b - * @param a Block a - * @param b Block b - * @return The dominator of a and b - */ - private static BasicBlock dom(BasicBlock a, BasicBlock b) { - while (a!=b) { - if (a.depth >= b.depth) a=a.dom; - if (b.depth > a.depth) b=b.dom; - } - return a; - } - - /** - * Return if a is before b. - * @param a Block a - * @param b Block b - * @return true is a is before b. - */ - private static boolean isBefore(BasicBlock a, BasicBlock b) { - while (b.depth > a.depth) { - while (b.dom.depth > a.depth) b = b.dom; - for (int i=0; i{ - // before might be in a different branch. So check this case with isBefore - // and only refine the placement if it is not in a different branch. - if (isBefore(d.block, data.block)) refinePlacement(data, d.block); - }); - } - - /** - * Schedule all nodes not yet scheduled. - */ - private void doSchedule() { - while (!scheduleQueue.isEmpty()) { - var data = scheduleQueue.pop(); - assert !isPinnedNode(data); - - assert data.block != null; - assert data.users == 0; - - if (data.node instanceof LoadNode l) { - assert isValid(data); - // Handle anti-deps of load nodes. - // At this point all anti-dep nodes are scheduled, - // but they did not refine the placement - // so do that now. - var mem = l.in(1); - for(var out : mem._outputs) { - // Aggregates describe partitions; they do not overwrite - // memory. Only users of this alias constrain the load. - if (out instanceof MemMergeNode || out instanceof ReturnNode) continue; - if (out instanceof StoreNode s && s._alias != l._alias) continue; - if (out instanceof MemPhiNode p && p._alias != l._alias) continue; - if (out instanceof BulkMemPhiNode p && p.isSplit(l._alias)) continue; - if (out instanceof PhiNode p) { - var r = p.in(0); - for (int i = 1; i < p.nIns(); i++) { - if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); - } - } else if (!(out instanceof LoadNode)) { - optionalRefinePlacement(data, out); - } - } - } - - assert isValid(data); - data.block.reverseSchedule.add(data.node); - - for(var in:data.node._inputs) { - if (in!=null) update(d(in), data.block); - } - if (data.node instanceof StoreNode s) { - // Store nodes have anti-deps to load nodes. - // So decrease the uses of these loads when the store is placed. - for (var out: s.in(1)._outputs) { - if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); - } - } - } - - // Now all nodes should be placed and have a block assigned - assert this.data.values().stream().noneMatch(d->d.block==null); - } - - /** - * Decrement the not-yet-placed users of a block. - * @param data The node for which the users should be decremented. - */ - private void decUsers(NodeData data) { - assert data.users > 0; - if (--data.users == 0) { - assert data.block != null; - assert isValid(data); - - // When all users are gone this node can be scheduled. - scheduleQueue.push(data); - } - } - - /** - * Update the placement of a node - * @param data The node to update - * @param block The block before which the node should happen. - */ - private void update(NodeData data, BasicBlock block) { - assert block != null; - if (data.users == 0) { - assert isPinnedNode(data); - return; - } - refinePlacement(data, block); - decUsers(data); - } - - /** - * Checks if a CFG node is ready to be placed. This is used for regions joining branches. - * @param node The CFG node to check - * @return true if all parents of a CFG node are placed. - */ - private boolean isCFGNodeReady(Node node) { - if (node instanceof LoopNode l) { - assert d(l.in(1)).block != null; - } else if (node instanceof RegionNode r) { - for(int i=1; id.block==null).orElse(false)) return false; - } - } else { - assert d(node.in(0)).block != null; - } - return true; - } - - /** - * Schedule all phi nodes. - * @param phiQueue List of all the phi nodes to schedule. - */ - private void schedulePhis(Stack phiQueue) { - while(!phiQueue.empty()) { - var data = phiQueue.pop(); - var phi = (PhiNode) data.node; - var r = phi.in(0); - data.block = d(r).block; - for(int i=1; iupdate(d(in), d.block)); - } - } - } - - /** - * Build the control flow graph. - * @param start The start node. - */ - private void doBuildCTF(StartNode start) { - var queue = new Stack(); - var phiQueue = new Stack(); - queue.push(d(start)); - int depth = 0; - while (!queue.isEmpty()) { - var data = queue.pop(); - var node = data.node; - assert node.isCFG(); - BasicBlock block; - switch (node) { - case StartNode s: - block = new BasicBlock(s, depth++); - break; - case LoopNode l: - block = new BasicBlock(l, depth++, d(l.in(1)).block); - break; - case RegionNode r: - var prev = new ArrayList(); - for(int i=1; iprev.add(d.block)); - } - block = new BasicBlock(r, depth++, prev.toArray(BasicBlock[]::new)); - break; - case IfNode i: - block = d(i.in(0)).block; - break; - case ReturnNode r: - block = d(r.in(0)).block; - break; - default: - block = new BasicBlock(node, depth++, d(node.in(0)).block); - } - data.block = block; - if (node instanceof RegionNode r) { - // Regions might have phis which need to be scheduled. - // Put them on a list for later scheduling. - for (var out:r._outputs) { - if (out instanceof PhiNode p) { - var d = this.data.get(p); - if (d!=null) { - d.users=0; - phiQueue.push(d); - } - } - } - } - if (!(node instanceof ReturnNode)) - for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); - var b = block; - for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); - } - - // Schedule all phi nodes. - schedulePhis(phiQueue); - } - - /** - * Mark a node alive and create ancillary data for it. - * @param queue List into which this node should be placed if it needs to be visited. - * @param node Node which should be marked alive. - * @param cfg If this node is a CFG node. - */ - private void markAlive(Stack queue, Node node, boolean cfg) { - var nd = od(node).orElse(null); - if (nd != null) { - // Node was already visited, just increase the users. - if (nd.users>0) nd.users++; - return; - } - assert node.isCFG() == cfg; - assert isNotXCtrl(node); - nd = new NodeData(node); - if (cfg) nd.users=0; - data.put(node, nd); - queue.push(nd); - } - - /** - * Mark all alive nodes. - * First all CFG nodes are marked, then all other nodes. - * @param node THe start node. - */ - private void doMarkAlive(Node node) { - var cfgQueue = new Stack(); - var dataQueue = new Stack(); - var mem = new Stack(); - markAlive(cfgQueue, node, true); - // Mark all CFG nodes. - while (!cfgQueue.isEmpty()) { - var data = cfgQueue.pop(); - node = data.node; - assert node.isCFG(); - if (!(node instanceof ReturnNode)) { - for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); - } - for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); - } - // Mark all other nodes. - while (!dataQueue.isEmpty()) { - var data = dataQueue.pop(); - node = data.node; - assert !node.isCFG(); - if (node instanceof PhiNode phi) { - var r = phi.in(0); - for (int i=1; id.users++); - } - } - } - - /** - * Helper function to append the nodes of a block to an array of nodes - * @param arr The array the nodes of a block should be appended to. - * @param node A potentially separator node - * @param block The block from which the nodes should be appended. - * @return The combined new array. - */ - private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { - var idx = arr.length; - arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); - if (node != null) arr[idx++] = node; - for(int i=block.reverseSchedule.size()-1;i>=0;i--) { - arr[idx++] = block.reverseSchedule.get(i); - } - return arr; - } - - /** - * Find the CFG output of a node - * @param node The node - * @return THe CFG output of the node. - */ - private static Node findSingleCFGOut(Node node) { - if (node instanceof StartNode) { - for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; - return null; - } - assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; - for(var n:node._outputs) if(n.isCFG()) return n; - return null; - } - - /** - * Build the final data structure after all the scheduling happened. - * @param start The start node - * @return The final schedule. - */ - private Block build(Node start) { - var blocks = new IdentityHashMap(); - var queue = new Stack(); - queue.push(d(start)); - - // Visit all CFG nodes and create blocks for them. - // This can combine blocks. - while(!queue.isEmpty()) { - var data = queue.pop(); - var first = data.node; - Node prev; - var last = first; - Node[] arr = new Node[0]; - if (first instanceof RegionNode) { - var nodes = new ArrayList(); - for(var out: first._outputs) { - if (out instanceof PhiNode) { - if(od(out).isPresent()) nodes.add(out); - } - } - arr = nodes.toArray(arr); - } - arr = appendNodes(arr, null, data.block); - assert !(first instanceof IfNode); - for(;;) { - prev = last; - last = findSingleCFGOut(last); - if (last == null) break; - if (last instanceof RegionNode) { - if (blocks.get(last) == null && last != first) - queue.push(d(last)); - break; - } - if (last instanceof ReturnNode) break; - if (last instanceof IfNode if_) { - for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out)); - break; - } - data = d(last); - arr = appendNodes(arr, data.node, data.block); - } - blocks.put(first, switch(last) { - case null -> new Block(arr, null, -1, new Block[0]); - case IfNode i -> new Block(arr, i, -1, new Block[2]); - case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); - case ReturnNode r -> new Block(arr, r, -1, new Block[0]); - default -> throw new AssertionError("Unexpected block exit node"); - }); - } - // Update the next pointer of all blocks. - for (var block: blocks.values()) { - switch(block.exit) { - case null: break; - case IfNode i: - for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); - break; - case RegionNode r: - block.next[0] = blocks.get(r); - break; - default: - assert block.exit instanceof ReturnNode; - } - } - // And return the block for the start node. - return blocks.get(start); - } - - /** - * Create a schedule for the program reachable from start. - * @param start The start node - * @return The final schedule. - */ - public static Scheduler.Block schedule(StartNode start) { - var scheduler = new Scheduler(); - scheduler.doMarkAlive(start); - scheduler.doBuildCTF(start); - scheduler.doSchedule(); - return scheduler.build(start); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java deleted file mode 100644 index 7dcb88e5..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.Random; -import java.util.function.Consumer; - -/** - * Implementation of the fuzzer. - * Random testcases are generated using the ScriptGenerator. - * This will generate a script similar to how the parser parses it but instead of parsing it will - * randomly choose a variation the parser would parse and generate it. - * These scripts are the parsed by the parser and all exceptions are caught and filtered - * to only show one occurrence of one problem. This includes script which generate different - * results when graphs optimized with different worklist seeds are executed. - * To aid debugging scripts that cause errors are then reduced by applying rules and checking - * that the same issue persists. - */ -public class Fuzzer { - - private static final int EVAL_TIMEOUT = 1000; - - /** - * List of exceptions already encountered. - * This is used to filter new exceptions and discard them if they are already found. - */ - private final ArrayList exceptions = new ArrayList<>(); - - /** - * Filter and record an exception in a script. - * @param e The exception caused by the script - * @param script The script causing the exception - * @param reproducer Reproducer which should be used to reproduce the test case. - */ - private void recordException(Throwable e, String script, Consumer reproducer, long seed) { - for (var ex : exceptions) { - if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; - } - exceptions.add(e); - System.out.println("========== Stack =========="); - e.printStackTrace(System.out); - System.out.println("========== Seed =========="); - System.out.println(seed); - System.out.println("========== Code ==========="); - System.out.println(script); - System.out.println("========= Reduced ========="); - System.out.println(Reducer.reduce(script, e, reproducer)); - System.out.println("==========================="); - System.out.flush(); - } - - private static boolean neq(Object a, Object b) { - if (Objects.equals(a, b)) return false; - if (a instanceof Evaluator.Obj ea && b instanceof Evaluator.Obj eb) { - if (ea.struct() != eb.struct()) return true; - for(int i=0;irunCheck(s, valid), seed); - } - } - - /** - * Run one test with the given seed, and check for peephole counts. - * @param seed The seed to use for generating this test case - */ - public int fuzzPeepTiming(long seed, int max_nids) { - var rand = new Random(seed); - var sb = new StringBuilder(); - new ScriptGenerator(rand, sb, true).genProgram(); - try { - var parser = new Parser(sb.toString()); - - var stop = parser.parse(); - int parse_peeps= Node.ITER_CNT; - int parse_nops = Node.ITER_NOP_CNT; - double parse_nop_ratio = (double)parse_nops/parse_peeps; - int nids = Node.UID(); - if( nids <= max_nids ) return max_nids; - - double parse_peeps_per_node = (double)parse_peeps/nids; - - stop.iterate(); - int iter_peeps= Node.ITER_CNT; - int iter_nops = Node.ITER_NOP_CNT; - double iter_nop_ratio = (double)iter_nops/iter_peeps; - double iter_peeps_per_node = (double)iter_peeps/nids; - - System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n", - seed, - parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node, - iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node); - return nids; - } catch (Throwable e) { - // ignore exceptions, let the normal fullPeeps catch and reduce, - // we're interested in valid program optimizations - } - return max_nids; - } - - - /** - * Run one test with the given seed. - * @param seed The seed to use for generating this test case - */ - public void fuzzPeeps(long seed) { - var rand = new Random(seed); - var sb = new StringBuilder(); - var valid = new ScriptGenerator(rand, sb, true).genProgram(); - check(sb.toString(), valid, seed); - } - - - /** - * Check that no exceptions happened. - * @return true if no exception happened - */ - public boolean noExceptions() { - return exceptions.isEmpty(); - } - - /** Run a fixed chapter-local seed without reducing or swallowing failures. */ - public void fuzzPeepsRegression(long seed) { - var rand = new Random(seed); - var sb = new StringBuilder(); - var valid = new ScriptGenerator(rand, sb, true).genProgram(); - try { - runCheck(sb.toString(), valid); - } catch (Throwable e) { - AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed); - ae.initCause(e); - throw ae; - } - } -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java deleted file mode 100644 index f947a234..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -import java.io.OutputStream; -import java.io.PrintStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; -import java.util.BitSet; - -/** - * Some utilities for the fuzzer. - */ -class FuzzerUtils { - - /** - * Get the val of a private field - */ - @SuppressWarnings("unchecked") - public static T getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { - return (T) (obj instanceof Class clz ? getField(clz, name).get(null) : getField(obj.getClass(), name).get(obj)); - } - - /** - * Get access to a private field - */ - public static Field getField(Class clz, String name) throws NoSuchFieldException { - var field = clz.getDeclaredField(name); - try { - field.setAccessible(true); - } catch (Throwable e) { - // Intentionally left empty - } - return field; - } - - /** - * Try to rethrow an exception or create a runtime exception and throw it. - */ - public static RuntimeException rethrow(Throwable e) { - if (e instanceof RuntimeException re) return re; - if (e instanceof Error er) throw er; - return new RuntimeException(e); - } - - /** - * Copy of Node.WVISIST - * Used to clear as exceptions might happen in the walk and left this bitset not cleared - */ - private static final BitSet NodeWalkVisit; - private static final java.util.Random WorkRandom; - - /** - * Write access to Iterate.MID_ASSERT - * Exception could happen mid assert and left this at true. Used to reset to false prior to parsing. - */ - private static final MethodHandle set_MID_ASSERT; - - /** - * Some problems might trigger debug output. Suppress this output with a null print stream during fuzzing. - */ - public static final PrintStream NULL_PRINT_STREAM = new PrintStream(OutputStream.nullOutputStream()); - - static { - try { - NodeWalkVisit = getFieldValue(Node.class, "WVISIT"); - WorkRandom = getFieldValue(getFieldValue(IterPeeps.class, "WORK"), "_R"); - set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT")); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * Check that the messages are similar. - */ - private static boolean similarMessages(String a, String b) { - if (a == null || b == null) return true; - a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); - b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); - return a.equals(b); - } - - /** - * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization - * and allow the other to be any stack trace. - */ - private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) { - if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing. - return a[0].equals(b[0]); - } - - /** - * Checks if two exceptions are caused by the same problem. - */ - public static boolean isExceptionFromSameCause(Throwable a, Throwable b) { - if (a == null || b == null) return a == b; // Handle the null case - if (a.getClass() != b.getClass()) return false; // Different classes - if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard - var s1 = a.getStackTrace(); - var s2 = b.getStackTrace(); - if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins - if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes - var o1 = a.getSuppressed(); - var o2 = b.getSuppressed(); - if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression - for (int i=0; i {} - - /** - * Holder for the rewrite rules - */ - private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) { - Replace(Pattern p, Object... replacements) { - this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new)); - } - private static Replacer from(Object o) { - if (o instanceof Replacer r) return r; - if (o instanceof Integer i) return (s, m)->s.append(m.group(i)); - if (o instanceof String r) return (s, m)->s.append(r); - throw new IllegalArgumentException(); - } - } - - /** - * List of rewrite rules. - */ - private static final Replace[] REPLACEMENTS = { - new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"), - new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""), - new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), - new Replace(Pattern.compile("\\b--"), "+"), - new Replace(Pattern.compile("-"), ""), - new Replace(Pattern.compile("\\b\\+-"), "-"), - new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), - new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1), - new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2), - new Replace(Pattern.compile("\\(([^()]+)\\)"), 1), - new Replace(Pattern.compile("\\d+\\+"), ""), - new Replace(Pattern.compile("\\+\\d+"), ""), - new Replace(Pattern.compile("\\n\\n+"), "\n"), - new Replace(Pattern.compile("^\\n+|\\n+$"), ""), - new Replace(Pattern.compile("else *"), ""), - new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "") - }; - - /** - * Predicate to test if the reduced script generates the same error. - */ - private final Predicate tester; - - /** - * Create a reducer with a test function. - * @param tester Test function which returns true when the same issue is found in the script. - */ - private Reducer(Predicate tester) { - this.tester = tester; - } - - /** - * Rewrites the script with one rewrite rule. - */ - private String doReplacement(String script, Pattern pattern, Replacer[] replacements) { - var matcher = pattern.matcher(script); - var sb = new StringBuilder(); - var last = 0; - var skip = 0; - outer: - while (matcher.find(last + skip)) { - sb.append(script, last, matcher.start()); - last = matcher.end(); - skip = last == matcher.start() ? 1 : 0; - var pos = sb.length(); - for (var replacement : replacements) { - replacement.accept(sb, matcher); - var tail = sb.length(); - sb.append(script, last, script.length()); - if (tester.test(sb.toString())) { - sb.setLength(tail); - continue outer; - } - sb.setLength(pos); - } - last = matcher.start(); - skip = 1; - } - sb.append(script, last, script.length()); - return sb.toString(); - } - - /** - * Clean up variable names from the random ones generated by the fuzzer. - */ - private String cleanVariables(String script) { - var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) ([0-9a-zA-Z_]+)").matcher(script); - int[] num = {0, 0}; - while (matcher.find()) { - var type = matcher.group(1); - var var = matcher.group(2); - if (type.equals("new")) continue; - String prefix = "v"; - int t = 0; - if (type.equals("struct")) { - t = 1; - prefix = "s"; - } - if (var.matches(prefix+"\\d+")) continue; - while (script.contains(prefix+num[t])) num[t]++; - var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]); - if (tester.test(n)) { - script = n; - } - } - return script; - } - - /** - * Run all rewrite rules once. - */ - private String doAllReplacements(String script) { - for (var replace: REPLACEMENTS) { - String old; - do { - old = script; - script = doReplacement(script, replace.pattern, replace.replacements); - } while (replace.iterate && !old.equals(script)); - } - return script; - } - - /** - * Reduce the script to a minimal version which produces the same problem with a test function. - * @param tester Test function which returns true when the same issue is found in the script. - */ - public static String reduce(String script, Predicate tester) { - var reducer = new Reducer(tester); - String old; - do { - old = script; - script = reducer.doAllReplacements(script); - script = reducer.cleanVariables(script); - } while (!script.equals(old)); - return script; - } - - /** - * Reduce the script to a minimal version which produces the same problem with a reproducer. - * @param ex The exception originally caused by the script - * @param reproducer Test function which throws an exception which will be checked against ex. - */ - public static String reduce(String script, Throwable ex, Consumer reproducer) { - return reduce(script, s->{ - Throwable nex = null; - try { - reproducer.accept(s); - } catch (Throwable e) { - nex = e; - } - return FuzzerUtils.isExceptionFromSameCause(nex, ex); - }); - } - -} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java deleted file mode 100644 index 155063ab..00000000 --- a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ /dev/null @@ -1,792 +0,0 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.ScopeNode; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Random; -import java.util.function.Predicate; - -/** - * Generate a pseudo random script. - * This generator will generate the same script for two random number generators initialized with the same seed. - * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions. - * It is guaranteed to terminate. - */ -public class ScriptGenerator { - - private static final int MAX_STATEMENTS_PER_BLOCK = 5; - private static final int MAX_BINARY_EXPRESSIONS_PER_EXPRESSION = 10; - private static final int MAX_UNARY_EXPRESSIONS_PER_EXPRESSION = 6; - private static final int MAX_NAME_LENGTH = 10; - private static final int MAX_BLOCK_DEPTH = 4; - private static final int MAX_EXPRESSION_DEPTH = 10; - - /** - * Number of spaces per indentation - */ - private static final int INDENTATION = 4; - /** - * Valid characters for identifiers. The last 10 are not valid for the first character. - */ - private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; - /** - * List of keywords not valid for identifiers. - */ - private static final HashSet KEYWORDS; - - static { - try { - KEYWORDS = FuzzerUtils.getFieldValue(new Parser(""), "KEYWORDS"); - } catch (Throwable e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * Flag for a statement that it does not pass on control flow to the next statement. - */ - private static final int FLAG_STOP = 0x01; - /** - * Flag for a statement that it contains a final if statement without an else branch. - */ - private static final int FLAG_IF_WITHOUT_ELSE = 0x02; - /** - * Binary operators. - */ - private static final String[] BINARY_OP = {"==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"}; - /** - * Unary operators. - */ - private static final String[] UNARY_OP = {"-"}; - - private static class Type { - final String name; - Type(String name) {this.name=name;} - boolean isa(Type other) { return this == other; } - } - - private static class TypeStruct extends Type { - record Field (String name, Type type) {} - final Field[] fields; - final TypeNullable nullable = new TypeNullable(this); - TypeStruct(String name, Field[] fields) { - super(name); - this.fields = fields; - } - boolean isa(Type other) { return this == other || other == nullable; } - } - - private static class TypeNullable extends Type { - - final Type base; - - TypeNullable(Type base) { - super(base.name+"?"); - this.base = base; - } - - } - - private static final Type TYPE_INT = new Type("int"); - - - private static class Variable { - final String name; - final Type declared; - final Variable shadowing; - Type type; - boolean shadowed = false; - - Variable(String name, Type type, Variable var) { - this.name = name; - this.declared = type; - this.type = type; - this.shadowing = var; - } - } - - /** - * The random number generator used for random decisions. - */ - private final Random random; - /** - * Indentation depth. - */ - private int indentation = 0; - /** - * Output string builder - */ - private final StringBuilder sb; - /** - * Number of nested loops - */ - private int loopDepth = 0; - /** - * Current scope start in the variables list. - */ - private int currScopeStart = 0; - /** - * The depth of blocks allowed. - */ - private int depth = MAX_BLOCK_DEPTH; - /** - * The depth of expressions allowed. - */ - private int exprDepth = MAX_EXPRESSION_DEPTH; - /** - * Current variables in scope. - */ - private final ArrayList variables = new ArrayList<>(); - /** - * All defined structs. - */ - private final ArrayList structs = new ArrayList<>(); - /** - * Allow to declare structs. - */ - private boolean allowStructs = true; - /** - * If this needs to generate valid scripts or is also allowed to generate invalid ones. - */ - private boolean generateValid; - /** - * If this script might be invalid. - */ - private boolean maybeInvalid = false; - - - /** - * - * @param random the random number generator. - * @param sb the script will be written to this string builder. - * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs. - */ - public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) { - this.random = random; - this.sb = sb; - this.generateValid = generateValid; - } - - /** - * Random number with log distribution - * @param max The maximum value - * @return A random number in the range [0, max) with higher numbers being more uncommon. - */ - private int randLog(int max) { - max--; - int n = 1<=0; i--) { - if ((num & (1<2) return false; - maybeInvalid = true; - return true; - } - - /** - * Lookup a variable by name - * @param name name of the variable - * @return The index of the variable in variables or -1 - */ - private int lookup(String name) { - for (int i=variables.size()-1; i>=0; i--) { - if (variables.get(i).name.equals(name)) return i; - } - return -1; - } - - /** - * Lookup a variable by name - * @param name name of the variable - * @return THe variable of null if not found - */ - private Variable lookupVar(String name) { - int idx = lookup(name); - return idx == -1 ? null : variables.get(idx); - } - - /** - * Add a variable to the current scope - * @param name The name of the variable - * @param type The declared type of the variable - */ - private void addVariable(String name, Type type) { - var old = lookupVar(name); - variables.add(new Variable(name, type, old)); - if (old != null) old.shadowed = true; - } - - /** - * Pop variables to level to - * @param to Level until which variables should be popped - */ - private void popVariables(int to) { - while (variables.size() > to) { - var v = variables.removeLast(); - if (v.shadowing != null) v.shadowing.shadowed = false; - } - } - - /** - * Get a random visible variable patching pred - * @param pred The predicate the variable needs to match - * @return A random visible variable matching pred or null if no variable mached pred. - */ - private Variable findVisibleVariablesMatching(Predicate pred) { - int num=0; - for (var v:variables) { - if (!v.shadowed && pred.test(v)) num++; - } - if (num == 0) return null; - num = random.nextInt(num); - for (var v:variables) { - if (!v.shadowed && pred.test(v) && num-- == 0) return v; - } - throw new AssertionError(); - } - - /** - * Get a structure by name - * @param name Name of the structure - * @return The structure of null - */ - private TypeStruct getStruct(String name) { - for (var s:structs) { - if (s.name.equals(name)) return s; - } - return null; - } - - /** - * Generate a variable name. This might be new random name or a name from an outer scope. - * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope. - * @return The variable name. - */ - private String getVarName() { - if (currScopeStart > 0 && random.nextInt(10) > 7) { - // Use a variable outside the current scope. - var v = variables.get(random.nextInt(currScopeStart)); - if (lookup(v.name) < currScopeStart) return v.name; - if (!generateValid) { - maybeInvalid = true; - return v.name; - } - } - // Generate a new random variable name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - /** - * Get a random structure name - * @return A random structure name - */ - private String getStructName() { - if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name; - // Generate a new random struct name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || getStruct(v) != null) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - /** - * Get a random type - * @return A random type - */ - private Type getType() { - if (structs.isEmpty() || random.nextBoolean()) return TYPE_INT; - var idx = random.nextInt(structs.size()); - TypeStruct struct = structs.get(idx); - if (random.nextBoolean()) return struct.nullable; - return struct; - } - - /** - * Generates a program and writes it into the string builder supplied in the constructor. - * @return If the program is allowed to be invalid returns if something potentially invalid was generated. - */ - public boolean genProgram() { - var oldGenerateValid = generateValid; - if (!generateValid && random.nextBoolean()) generateValid = true; - this.maybeInvalid = false; - addVariable(ScopeNode.ARG0, TYPE_INT); - currScopeStart = variables.size(); - if ((genStatements() & FLAG_STOP) == 0) { - if (generateValid || random.nextInt(10)<7) { - genReturn(); - } else { - this.maybeInvalid = true; - } - } - generateValid = oldGenerateValid; - return !this.maybeInvalid; - } - - /** - * Generate a list of statements. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated - */ - public int genStatements() { - var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1)); - for (int i=0; i allowStructs || generateInvalid() ? genStruct() : genAssignment(); - case 1 -> genBlock(); - case 2 -> genIf(); - case 3 -> genCountedLoop(); - case 4 -> genWhile(); - case 5, 6 -> genDecl(); - case 7, 8, 9 -> genAssignment(); - case 10 -> genNullCheck(); - default -> genExit(); - }; - } - - /** - * Generate a new structure - * @return 0 - */ - public int genStruct() { - var name = getStructName(); - var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)]; - sb.append("struct ").append(name).append(" {\n"); - indentation += INDENTATION; - for (int i=0; i { - depth++; - indentation -= INDENTATION; - var ret = genBlock(); - indentation += INDENTATION; - depth--; - yield ret; - } - case 6 -> genIf(); - case 7 -> genCountedLoop(); - case 8 -> genWhile(); - case 9 -> genAssignment(); - case 10 -> genNullCheck(); - default -> genExit(); - }; - depth++; - } - indentation -= INDENTATION; - allowStructs = oldAllowStructs; - return res; - } - - /** - * Generate an exit. This can be return or break and continue when in a loop. - * @return FLAG_STOP - */ - public int genExit() { - if (loopDepth == 0 && generateValid) return genReturn(); - return switch (random.nextInt(7)) { - case 0,1,2 -> { - if (loopDepth == 0) maybeInvalid = true; - sb.append("continue;"); - yield FLAG_STOP; - } - case 3,4,5 -> { - if (loopDepth == 0) maybeInvalid = true; - sb.append("break;"); - yield FLAG_STOP; - } - default -> genReturn(); - }; - } - - /** - * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator. - * @return flag FLAG_STOP for the generated statement - */ - public int genBlock() { - if (depth == 0) { - sb.append("{}"); - return 0; - } - var oldAllowStructs = allowStructs; - var oldCSS = currScopeStart; - allowStructs = false; - currScopeStart = variables.size(); - depth--; - sb.append("{\n"); - indentation += INDENTATION; - var stop = genStatements(); - indentation -= INDENTATION; - printIndentation().append("}"); - popVariables(currScopeStart); - depth++; - currScopeStart = oldCSS; - allowStructs = oldAllowStructs; - return stop & ~FLAG_IF_WITHOUT_ELSE; - } - - /** - * Generate an if statement with an optional else block. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement - */ - public int genIf() { - sb.append("if("); - genExpression(TYPE_INT); - sb.append(") "); - var stop = genStatementBlock(); - if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { - sb.append("\n"); - printIndentation().append("else "); - stop &= genStatementBlock(); - } else { - stop = FLAG_IF_WITHOUT_ELSE; - } - return stop; - } - - /** - * Generate a block with a null check - * if (nullable) { handle_nullable_as_non_nullable; } - */ - public int genNullCheck() { - var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable); - if (v == null) return genIf(); - var n = (TypeNullable)v.declared; - boolean negate = random.nextBoolean(); - sb.append("if("); - if (negate) sb.append("!"); - sb.append(v.name).append(")"); - if (!negate) v.type = n.base; - var stop = genStatementBlock(); - v.type = n; - if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { - sb.append("\n"); - printIndentation().append("else "); - if (negate) v.type = n.base; - stop &= genStatementBlock(); - v.type = n; - } else { - stop = FLAG_IF_WITHOUT_ELSE; - } - return stop; - } - - /** - * Generate a while loop. - * @return 0 - */ - public int genWhile() { - sb.append("while("); - genExpression(TYPE_INT); - sb.append(") "); - loopDepth++; - genStatementBlock(); - loopDepth--; - return 0; - } - - /** - * Generate a counted loop of the form - * - * { - * int varname = start_expr; - * while (varname < end_expr) loop_body; - * } - * - * @return 0 - */ - public int genCountedLoop() { - if (depth == 0) return genWhile(); - var oldCSS = currScopeStart; - currScopeStart = variables.size(); - sb.append("{\n"); - indentation += INDENTATION; - String name = getVarName(); - printIndentation().append("int ").append(name).append("="); - genExpression(TYPE_INT); - sb.append(";\n"); - addVariable(name, TYPE_INT); - printIndentation().append("while(").append(name).append("<"); - genExpression(TYPE_INT); - sb.append(") {\n"); - indentation += INDENTATION; - depth--; - loopDepth++; - printIndentation().append(name).append("=").append(name).append("+"); - genExpression(TYPE_INT); - sb.append(";\n"); - genStatements(); - loopDepth--; - depth++; - indentation -= INDENTATION; - printIndentation().append("}\n"); - indentation -= INDENTATION; - printIndentation().append("}"); - popVariables(currScopeStart); - currScopeStart = oldCSS; - return 0; - } - - /** - * Generate a declaration statement. - * @return 0 - */ - public int genDecl() { - var type = getType(); - var name = getVarName(); - sb.append(generateInvalid() ? getRandomName() : type.name).append(" ").append(name); - if (!(type instanceof TypeNullable) || random.nextBoolean()) { - sb.append("="); - genExpression(type); - } - sb.append(";"); - addVariable(name, type); - return 0; - } - - /** - * Generate an assignment statement. - * @return 0 - */ - public int genAssignment() { - Type type; - Type declared; - if (generateInvalid()) { - sb.append(getRandomName()); - type = getType(); - declared = type; - } else { - var v = findVisibleVariablesMatching(va->true); - if (v == null) return genDecl(); - sb.append(v.name); - type = v.type; - declared = v.declared; - } - if (generateInvalid()) { - var name = getRandomName(); - sb.append(".").append(name); - declared = getType(); - } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) { - var field = s.fields[random.nextInt(s.fields.length)]; - sb.append(".").append(field.name); - declared = field.type; - } - sb.append("="); - genExpression(declared); - sb.append(";"); - return 0; - } - - /** - * Generate a return statement. - * @return FLAG_STOP - */ - public int genReturn() { - var type = getType(); - sb.append("return "); - genExpression(type); - sb.append(";"); - return FLAG_STOP; - } - - /** - * Generate a binary expression. - * This method does not care about operator precedence. - */ - public void genExpression(Type type) { - if (generateInvalid()) type = getType(); - if (type != TYPE_INT) { - genUnary(type); - return; - } - var num = randLog(MAX_BINARY_EXPRESSIONS_PER_EXPRESSION); - while(num-->0) { - genUnary(TYPE_INT); - sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); - } - genUnary(TYPE_INT); - } - - /** - * Generate a unary expression. - */ - public void genUnary(Type type) { - if (generateInvalid()) type = getType(); - if (type != TYPE_INT) { - genSuffix(type); - return; - } - var num = randLog(MAX_UNARY_EXPRESSIONS_PER_EXPRESSION); - while(num-->0) { - sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); - } - genSuffix(TYPE_INT); - } - - /** - * Generate - */ - public void genSuffix(Type type) { - if (generateInvalid()) type = getType(); - if (type == TYPE_INT && random.nextBoolean()) { - var t = getType(); - if (generateInvalid()) { - var field = getRandomName(); - genPrimary(t); - sb.append(".").append(field); - return; - } else if (t instanceof TypeStruct s && s.fields.length > 0) { - var field = s.fields[random.nextInt(s.fields.length)]; - if (field.type == TYPE_INT) { - genPrimary(t); - sb.append(".").append(field.name); - return; - } - } - } - genPrimary(type); - } - - /** - * Generate a primary expression. - */ - public void genPrimary(Type type) { - if (generateInvalid()) type = getType(); - var rand = random.nextInt(10); - if (rand == 0 && exprDepth != 0) { - sb.append("("); - exprDepth--; - genExpression(type); - exprDepth++; - sb.append(")"); - } else if (rand < 6) { - genVariable(type); - } else { - genConst(type); - } - } - - /** - * Generate a constant. - */ - public void genConst(Type type) { - if (generateInvalid()) type = getType(); - if (type == TYPE_INT) { - var rand = random.nextInt(10); - switch (rand) { - case 0 -> sb.append("true"); - case 1 -> sb.append("false"); - default -> sb.append(random.nextInt(1<<(rand-2))); - } - } else if (type instanceof TypeNullable n) { - if (random.nextBoolean()) { - sb.append("null"); - } else { - sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name); - } - } else { - sb.append("new ").append(generateInvalid() ? getRandomName() : type.name); - } - } - - /** - * Generate a variable. If there are none generate a number instead. - * If the program is allowed to be invalid a random name can be generated in some cases. - */ - public void genVariable(Type type) { - if (generateInvalid()) { - sb.append(getRandomName()); - return; - } - if (generateInvalid()) type = getType(); - var t = type; - var v = findVisibleVariablesMatching(va->va.type.isa(t)); - if (v == null) genConst(type); - else sb.append(v.name); - } - -} diff --git a/chapter11/Makefile b/chapter11/Makefile index 3f1e53ff..5ed21084 100644 --- a/chapter11/Makefile +++ b/chapter11/Makefile @@ -1,106 +1,7 @@ -SHELL := /bin/bash -# Shared packaging target; included after chapter source variables below. -.DELETE_ON_ERROR: +# Standalone compiler snapshot; shared rules also work in linearized checkouts. +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) +test_cp += com.seaofnodes.simple.FuzzerWrap -# for printing variable values -# usage: make print-VARIABLE -# > VARIABLE = value_of_variable -print-% : ; @echo $* = $($*) - -# literal space -space := $() $() - -# Decide OS-specific questions -# jar-file seperator -ifeq ($(OS),Windows_NT) - SEP = ; -else - SEP = : -endif -# Find a reasonable ctags. -CTAGS = $(shell which ctags) -# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew -ifeq ($(UNAME),Darwin) - CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags) -endif - -# Fun Args to javac. Mostly limit to java8 source definitions, and fairly -# aggressive lint warnings. -JAVAC_ARGS = -g - -# Source code -SIMPLE := com/seaofnodes/simple -SRC := src/main/java -TST := src/test/java -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)) -include $(firstword $(wildcard ../print/print.mk print/print.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) -# All the libraries -libs = $(wildcard lib/*jar) -jars = $(subst $(space),$(SEP),$(libs)) - - -default_targets := $(main_classes) $(test_classes) -# Optionally add ctags to the default target if a reasonable one was found. -ifneq ($(CTAGS),) -default_targets += tags -endif - -default: $(default_targets) - -# Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) $(print_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) - -$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) - @echo "compiling " $@ " because " $? - @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test - @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) - -# Base launch line for JVM tests -JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" - -tests: $(default_targets) - @echo "testing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore $(test_cp) com.seaofnodes.simple.FuzzerWrap - -fuzzer: $(default_targets) - @echo "fuzzing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap - - -.PHONY: clean -clean: - rm -rf build - rm -f TAGS - (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0) - -# Download libs from maven -lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar - -# Jars -lib/junit-4.12.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar) - -lib/hamcrest-core-1.3.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar) - -# Build emacs tags (part of a tasty emacs ide experience) -tags: $(main_javas) $(test_javas) - @rm -f TAGS - @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) - -include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) - -.PHONY: build -build: $(main_classes) +.PHONY: fuzzer +fuzzer: $(test_classes) + java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter11/README.md b/chapter11/README.md index cd44f811..d3e8c7e1 100644 --- a/chapter11/README.md +++ b/chapter11/README.md @@ -1,366 +1,268 @@ -# Chapter 11: Global Code Motion +# Chapter 11: Splitting Memory -[Previous: Chapter 10b](../chapter10b/README.md) | +[Previous: Chapter 10](../chapter10/README.md) | [Next: Chapter 12](../chapter12/README.md) -This chapter schedules the programs from [10b](../chapter10b/README.md), -picking a slot#0 CFG input for all nodes lacking one. +Chapter 10 made memory effects explicit with one SSA memory value. This +chapter keeps exactly that parser model and recovers independent memory chains +through graph rewrites. There is no new language syntax; the +[grammar](docs/10-grammar.md), pointer types, and null checks are unchanged. -Also in this chapter, we'll be presenting several fairly detailed graph -algorithms. You might want to brush up on your graph theory! - -# Table of Contents - -1. [High Level Overview](#high-level-overview) -2. [Scheduling Walk Through](#scheduling-walk-through) -3. [Scheduling a Loop](#scheduling-a-loop) -4. [Components of the Global Code Motion Algorithm](#components-of-the-global-code-motion-algorithm) -5. [Identification of Basic Blocks in SoN graph](#identification-of-basic-blocks-in-son-graph) -6. [Handling Infinite Loops](#handling-infinite-loops) -7. [Dominators](#dominators) -8. [Loop depth](#loop-depth) -9. [Early Schedule](#early-schedule) -10. [Late Schedule](#late-schedule) -11. [Inserting Anti Dependencies](#inserting-anti-dependencies) -12. [Video Walk Through](#video-walk-through) - -You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter11) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter10b...linear-chapter11) it to the previous chapter. +## Why split memory? +```java +struct Point { int x; int y; } +Point p = new Point; +p.x = 42; +while (arg > 0) { + p.y = p.y + arg; + arg = arg - 1; +} +return p.x; +``` -The original input source program defines a sequence in which things happen. As -we parse the program into Sea of Nodes representation and perform various -optimizations, this sequence is not fully maintained. The optimized Sea of -Nodes graph is driven by the dependencies between nodes rather than the -sequence of instructions in the original source program. +The loop changes `y`, so it cannot affect `x`. With one memory chain the load +of `x` follows the loop's stores. Splitting the fields lets ordinary +load-after-store forwarding turn the return value into 42. -Our goal in this chapter is to look at how we can recover a schedule for executing instructions from an -optimized Sea of Nodes graph. This schedule needs to preserve the semantics of the original source program, -but is otherwise allowed to reorder the execution of instructions. +![Independent field dependencies](docs/example1.svg) -Since the scheduling algorithm works across the implicit Basic Blocks in the SoN Graph, -we call this a Global Code Motion algorithm; the scheduler can move instructions across Basic Block boundaries. +The graph shows the split chains before the final Load folds. Its memory input +goes straight to the Store of 42, bypassing the loop's `y` chain. As in +Chapter 10, small, detached True/False projections show the source branches +gating Loads and Stores. Other control-flow context, including Loops and their +Phi bindings, is omitted. The graph shows dependencies, not execution order. -The primary reference for this algorithm is the GCM GVN paper [^1]. However, the implementation in Simple has some -differences compared to the version described in the paper. +## Equivalence-class aliasing -Since this is a complex topic, we will present a high level summary first and then delve into the details. +Each struct field has an integer alias identity. Two different fields receive +different identities, even if they have the same spelling in different structs. +Two instances of the *same* struct share their field aliases. Thus different +classes never alias; accesses in the same class **may alias** (same-class does +not mean same-address). You still need to check for pointer identity. -## High Level Overview +The parser assigns field identities at struct declarations, beginning at alias#2. -The Sea of Nodes graph has two virtual graphs within it. +We do not allocate a Start projection, scope binding, or loop Phi for every +declared field or alias. Start supplies one memory value, Scope holds one +`$mem` binding, and Return consumes all of memory: `{ctrl, $mem, result}`. As +with Start's outputs, control is in slot 0 and memory in slot 1. The optimizer +introduces slices only where graph operations require them. -* There is a control flow graph, represented by certain node types, such as Start, If, Region, etc. -* There is a data graph, primarily driven by Def-Use edges between nodes that produce or consume values or memory. +## Three memory nodes -From a scheduling point of view, the control flow graph is fixed, and immovable. -However, the nodes that consume or produce data values/memory have some flexibility in terms of when they are executed. The goal of the -scheduling algorithm is to find the best placement of these nodes so that they are both optimum and correct. +| Node | Meaning | +|---|---| +| `MemMerge` | A complete memory state: a default plus precise alias overrides | +| `MemPhi` | One alias selected according to the incoming control path | +| `BulkMemPhi` | All aliases except an explicit exclusion set, selected by control | -At its core, the scheduling algorithm works in two phases: +`MemMerge` combines independent pieces of memory at one program point. It is +not a control-flow merge. For example, after storing to alias `x`: -* Schedule Early - in this phase, we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the - first control block where they are dominated by their inputs. -* Schedule Late - in this phase we work backwards from Stop, scheduling uses before definitions, and move data nodes to a block between the first block calculated above, - and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. - Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. +```text +prior = current memory +store = Store_x(prior, pointer, value) +current memory = MemMerge(default=prior, x=store) +``` -## Scheduling Walk Through +MemMerges have a default memory in slot 1, and precise aliases in other slots +if they differ from the default. Loads, Stores and MemPhis select their +precise input from any incoming MemMerge, using a precise alias input if +possible. -Before we describe the steps in detail, it is instructive to walk through an example and see the changes that occur to the SoN graph during scheduling. +The explicit aliases override the default: the alias slices are disjoint, +and their union covers all memory. Flattening stacked MemMerges preserves +entries that the outer MemMerge has not replaced. -To motivate the discussion, we will use this example program. +The graph below shows `p.x = arg; p.y = 42; return p;` after redundant +initialization stores have been removed. MemMerge takes the default memory +in slot 1 and the `x` and `y` overrides in slots 2 and 3. -```java -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -``` +![An aggregate preserves both updates](docs/example2a.svg) -First let's look at the graph before scheduling. -These walkthrough diagrams show the precise `S.f` chain and the final -MemMerge consumed by Return. The unchanged default memory comes from Start; -any redundant bulk Phis have folded away. As in 10a and 10b, the diagrams omit -control-flow context, including Regions and their Phi bindings. Small, -detached True/False projections remain where they gate a Load or Store. -Unlike Chapter 10's schematic source gates, these figures show the actual -scheduling inputs at each stage. Entry-block and Loop bindings are omitted. +## Splitting a memory Phi -![Graph1](./docs/graph1.svg) +The Parser initially creates a BulkMemPhi wherever the ordinary SSA algorithm needs +to merge memory. It starts by covering all aliases and an empty exclusion set +while the merge point is finalized. Later, peeps will split out precise memory +slices from the bulk memory, lazily on demand. -Observe that +Precise single aliases can be split out from the bulk by this transformation: -* Control nodes are immovable. Most are omitted here. -* New nodes already have control inputs, and Phis are bound to Regions; - these bindings remain fixed even though they are not drawn. -* The load `.f` and store `.f=` nodes are still "floating": they have no - control input at this stage, so scheduling must choose their blocks. +```text +BulkPhi(R, left, right) + becomes +MemMerge( + default = BulkPhi_except{x}(R, left, right), + x = MemPhi_x(R, slice(left,x), slice(right,x))) +``` -Now, let's look at the graph after we run the early schedule. +"A BulkPhi becomes a Merge of a BulkPhi (minus the alias) and MemPhi (of the alias)". -![Graph2](./docs/graph2.svg) +The new precise Phi and the remaining bulk Phi share a Region (omitted from the +illustrations) and matching control-path indices. A loop uses the same +transformation, with the backedge among those inputs. Each further split +preserves all previously extracted slices (and peephole optimizations tidy up +the stacked splits). +Here the two branches store 3 or 4 into `p.x`, then return `p`. The graph shows +the partition immediately after extracting `x`, before further simplification +of the bulk Phi and its incoming aggregates. -The early schedule puts the load `.f` and stores `.f=` in the first basic -block, since their inputs are available there. These entry-block control -bindings are omitted, so the memory view looks unchanged. +```java +struct Point { int x; int y; } +Point p = new Point; +if (arg) p.x = 3; +else p.x = 4; +return p; +``` -The graph below focuses on memory and data dependencies after late scheduling. -The control-flow scaffold, including the Region, is omitted. Store #22's -control input remains visible as the red edge to the standalone True -projection #8; the Store-to-Load anti-dependency and memory Phis are retained. +![Precise and bulk Phis share a control merge](docs/example2b.svg) -![Graph3](./docs/graph3.svg) +Continuing from this graph, here is one order in which the peepholes can fire: -The snip below shows the main changes in the graph: +**Flatten the incoming MemMerges, then split `y`.** MemMerges #17 and #18 +inherit the `y` slice from #9, but their own `x` overrides win. Both now have +Start's memory as their default, and #9 becomes unused. BulkMemPhi #21 excludes +`x`, but still covers `y`, so it cannot simply bypass these MemMerges. It +extracts MemPhi #24 for `y` and creates BulkMemPhi #25, excluding both fields. +The extra MemMerge #26 combines these slices. -![Store ordered after the earlier load](./docs/graph3-snip.svg) +![After flattening the incoming aggregates and extracting y](docs/example2b-split-y.svg) -* The store `.f=` now has an anti-dependency on the load `.f`; this ensures that the store is scheduled after the load, as required by program semantics. We discuss anti-dependencies in detail later. -* Observe also that the store `.f=` is now bound to the True branch of the If node. +**Fold the unchanged memory.** Both inputs of MemPhi #24 are the same +initializing Store #8, so it becomes that Store. BulkMemPhi #25 can now look +through #17 and #18: their explicit aliases are all excluded from its domain. +Both remaining inputs become Start's memory, so this bulk Phi also folds. +Flattening the outgoing aggregates leaves MemMerge #22 with default memory +from Start, `x` from MemPhi #20, and `y` from Store #8. -## Scheduling a Loop +![Only the x memory Phi still needs the branch merge](docs/example2b-merged.svg) -We show another example, this time involving a loop. +**Factor the Stores, then remove the overwritten initialization.** With the +bulk Phi and the incoming aggregates gone, Stores #14 and #16 are used only +by MemPhi #20. They store to the same pointer and alias, and share the same +incoming memory. `PhiNode` can pull the Store below the merge: -```java -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; +```text +MemPhi_x(Store_x(mem, p, 3), Store_x(mem, p, 4)) + becomes +Store_x(mem, p, Phi(3, 4)) ``` -The SoN graph prior to scheduling looks like this: +In general, Phi refactoring can combine Loads or Stores as in Chapter 10 (pushing +up or pulling down). The Phi new memory operands can use precise MemPhis for +the alias. This peep requires precise aliases, and is otherwise blocked by +BulkMems - and thus is a trigger for splitting a BulkMem. -![Graph4](./docs/graph4.svg) -Following early schedule generation, we get: +Continuing with the example, the new value Phi #28 chooses 3 on the true arm +and 4 on the false arm. Store +#27 replaces both branch Stores and their memory Phi. Now the initial +`p.x = 0` Store #7 has only that one Store as a user, so store-after-store +elimination bypasses it. The `p.y = 0` Store #8 remains: we return the object, +and its `y` field still needs initialization. -![Graph5](./docs/graph5.svg) +![Final graph: one x Store of a value Phi, and the y initialization](docs/example2b-final.svg) -The early schedule places Store #31 at the loop header (its control binding -is omitted). Late scheduling moves it to the True branch #15 of -`if (arg == 5)`, shown by the red control edge: +Thus the final graph has two Stores, one ordinary value Phi, and one MemMerge; +neither a MemPhi nor a BulkMemPhi remains. Other worklists order can, for +instance, bypass the original incoming MemMerges before flattening them, +avoiding the temporary `y` split. -![Graph6](./docs/graph6.svg) +This memory splitting transform is invasive, and is disallowed from being +called recursively; you can end up splitting around a loop (which is good) but +then trying to split a mid-split loop head (which is bad). The new nodes from +the split are put on the worklist and visited after the current split +completes - safely allowing memory to be completely sliced around loops. -The final execution schedule shows that the expression `i/3` can be performed outside the loop because it only depends on the -original value of `arg`, and hence is loop invariant. +Since bulk Phis merge a bunch of aliases, they have to take care when +peepholing vs MemMerges: there cannot be any overlap between the MemMerge +precise merges and the bulk Phi. -## Components of the Global Code Motion Algorithm +This also extends the worklist lesson from Chapter 9: a bulk Phi inspects its +users, so changes to a user's partition must wake the producer - job for +`addDep()`. Selecting a slice from a MemMerge can expose a new bulk-Phi +user; that selected definition must be queued too. In short, any time we're +working with MemMerge, MemPhi or BulkMemPhi we can expect some non-local +dependencies. -We have already alluded to several components of the GCM algorithm in passing above. Here we list them out as well as others we did not mention: +Memory splitting can increase node count - but Phis (Mem, Bulk or otherwise) +make no code. Progress comes from extracting an alias from a bulk Phi and +removing some Loads and Stores, rather than from requiring every rewrite +to shrink the graph. In other words, the total Load/Store count is going down +even if the MemPhi count is going up. -* The SoN graph has implicit basic blocks in the control flow nodes. For the GCM algo, we need to recognize these nodes more explicitly. -* When a program has an infinite loop, it poses a problem for the algo, as nodes can be unreachable from the Stop node. To work around this issue, we need to discover - infinite loops and create a dummy edge connecting the loop to the Stop node. -* Loops are already identified in the Simple SoN graph, so we do not need a loop discovery step. However, we need to compute the loop depth associated with each CFG node. -* In [Chapter 6](../chapter06/README.md) we explained the concept of Dominators. Dominators are key to the GCM algo, and we extend our incremental dominator discovery algo to - ensure that it meets the requirements of the algo. -* We already mentioned the two phases of the algo - the Early Scheduling and the Late Scheduling. -* In addition, we need to add anti-dependency edges between Loads and Stores in certain scenarios to enforce correct execution order. -* There are a few changes to our Node hierarchy to help us implement the GCM algo more conveniently. These changes do not conceptually alter the Node hierarchy we inherited from the previous chapters. -## Identification of Basic Blocks in SoN graph +## Types -The Sea of Nodes graph already captures the program's control flow graph. This information is implicit in the control nodes and edges from control nodes to other types of nodes. +The type lattice retains Chapter 10's six domains, colors, and notation: +`⊤` means top, `⊥` means bottom, and a suffix names the domain. The other five +domains are unchanged; however memory now adds a flat element `MEM#N` for each precise +alias between memory top (`TypeMem.TOP`) and memory bottom (`TypeMem.BOT`). -To recap, Control starts at the Start node, via a projection that is bound to the name `$ctrl`. As the control flows in the program, this name binding gets updated, and control is -passed around, until it reaches the Stop node. +![The type domains, including precise memory aliases](docs/lattice.svg) -The Basic Block structure of the CFG can be easily constructed by recognizing that certain control nodes start a Basic Block, whereas certain others end a BB. +The parallel `MEM#2`, `MEM#3`, and further alias elements describe independent +field slices. For the first `Point` declaration above, these two aliases identify +`Point.x` and `Point.y`. Meeting different aliases gives memory bottom; joining +them gives memory top. Each precise alias is its own dual. (And the ellipsis stands +for more parallel aliases, not a type representing a set of aliases). -Here is the list of all control nodes: -| Node | Starts a BB | Ends a BB | -|--------|-----------------------------------------|-----------| -| Start | Yes but only via the `$ctrl` projection | No | -| CProj | Yes if input control is an If node | No | -| Region | Yes | No | -| If | No | Yes | -| Return | No | Yes | -| Stop | Yes | Yes | +## Evaluation and ordering -Changes to Node hierarchy in this chapter: +MemMerge is a dependency node and not a clobber; it emits no code. Its +evaluator result is a memory token, while Store actually mutates the object's +fields. The scheduler must distinguish merging memory from overwriting memory. +Stores in different alias classes do not impose load/store anti-dependencies, +but Loads in the same class require anti-dependencies. -* All control nodes above now extend a base class CFG Node. This allows us to place common functionality of control flow nodes in the base class. -* The CProj node extends the regular Proj node and is used in following cases: - * The `$ctrl` projection off Start - * The True and False projections off If. -* The Never node is a special sub class of If that is used to handle infinite loops as explained later. -* The XCtrl node represents a dead control. +In `int old = p.x; p.x = arg; p.y = arg; return old;`, the Load must precede +the later `x` Store; the `y` Store has an independent memory chain. The graph +retains the Load to show this scheduling requirement before local folding. -## Handling Infinite Loops +![Ordering applies within the affected alias](docs/example2c.svg) -Here is an example of code that contains an infinite loop: +There are a lot of tests in this chapter, to cover independent-field folding, +parallel memory Phis, multiple field updates surviving at Return, early +returns, nested loops with break/continue, and reads before subsequent stores +through potentially aliased pointers. -```java -while (1) {} -return 0; -``` +## What remains for later chapters? -First let's look at the graph resulting from this: +Chapter 11 knows every access's alias while parsing. What is lazy here is the +construction of memory partitions. Chapter 25 also allows an existing access's +alias to become known *later* (using the same representation) and will allow +more incomplete types, module aliases, seperate compilation and a host of other +benefits. -![Graph7](./docs/graph7.svg) +Chapters 12-24 use this representation. Chapter 13 adds global code motion +and load/store anti-dependencies. Chapter 15 adds allocation with partial memory and typed +alias contents. Chapter 16 extends initialization to constructor values, and +Chapter 18 carries whole memory across function calls. -Now, look at the modified graph after we insert an edge from the infinite loop to Stop node. +## Build and test -![Graph8](./docs/graph8.svg) +With JDK 21 or newer, from this directory: -The implementation is in -[`LoopNode.forceExit()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L57). -It walks the backedge's dominator chain to look for an exit. If none exists, -it inserts a Never node and a never-taken return path, making the loop -reachable to the scheduler's backward walk. +```sh +make lib +make tests +make release +make view +``` -## Dominators +Make builds this complete snapshot without Maven. `make release` writes +`build/release/simple.jar`; `make view` launches the shared graph viewer. +Both the root Maven reactor and the supplied IDEA module descriptor include +the chapter. From the repository root, test both halves with: -In Simple, we compute Dominators incrementally. Our approach relies on the fact that we may delete parts of the graph during peepholes, -but we never introduce new control structure via peepholes. This allows us to use a simple approach described below. - -The CFG node is the base class for all control nodes. It maintains a conservative approximation of dominator depth via `_idepth`. This field is a cached value representing -the immediate dominator depth. Its initial value is `0`, which signifies that it has not yet been computed. The depth is computed on request. +```sh +make tests CHAPTERS="chapter10 chapter11" +``` -See [`CFGNode.idepth()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L51) -for the default computation and cache. The overrides in -[`RegionNode`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L94), -[`LoopNode`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L31), -[`StartNode`](src/main/java/com/seaofnodes/simple/node/StartNode.java#L36), and -[`StopNode`](src/main/java/com/seaofnodes/simple/node/StopNode.java#L57) -handle merges, loop entries, and the graph's endpoints. - -If portions of the control flow graph are deleted, then there will be gaps in the `_idepth`, but it still correctly reflects the -invariant that the value of `_idepth` increases as we go down the dominator tree. - -Alongside the dominator depth, which is cached on first compute, a method is provided to get the immediate Dominator node. This -value is not cached as it is only valid at a point in time, and is invalidated as the graph changes. - -[`CFGNode.idom()` and `domLCA()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L60) -provide the default immediate dominator and the walk that finds the least -common ancestor of two dominators. -[`RegionNode.idom()`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L103) -combines the incoming paths; [`LoopNode.idom()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L33) -uses the loop entry. Start and Stop have no immediate dominator. - -## Loop Depth - -Simple's Sea of Nodes graph identifies loops explicitly via Loop nodes. Since the language provides a single way to create a loop, using the `while` statement, -it is not necessary to implement a generic loop discovery process. - -We do however need to compute a loop depth. This is done similarly to how we compute the dominator depth. - -[`CFGNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L77) -inherits depth from its control input, while -[`RegionNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L116) -uses an incoming path. -[`LoopNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L35) -adds a nesting level and walks the backedge's dominator chain to mark loop exits. -[`StartNode`](src/main/java/com/seaofnodes/simple/node/StartNode.java#L41) and -[`StopNode`](src/main/java/com/seaofnodes/simple/node/StopNode.java#L67) -establish the outermost depth of 1. - -## Early Schedule - -The GCM algorithm proper starts with the computation of the early schedule, during which we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the -first control block where they are dominated by their inputs. - -A pre-condition of this is to ensure that infinite loops have been fixed as described earlier. - -The implementation starts in -[`GlobalCodeMotion.schedEarly()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L59). -[`_rpo_cfg()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L82) -builds the CFG traversal order, and -[`_schedEarly()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L91) -recursively schedules data inputs. It skips recursion through Phi inputs to -avoid entering a data cycle before its control has been scheduled. - -Existing control and Phi/Proj bindings are preserved. Floating nodes receive -the deepest input block as their earliest placement. MemMerge follows this -same rule, ignoring absent entries in its sparse alias table. - -## Late Schedule - -Late scheduling starts at Stop and uses a worklist to place uses before their -definitions. CFG nodes, Phis, projections, and allocations provide fixed -placements. Other nodes wait until their uses have been scheduled; a Load also -waits for memory users that can overwrite or merge its alias. The chosen block -lies between the early placement and the common dominator of all uses, favoring -shallower loops and then deeper control flow. - -The implementation is divided into these steps: - -* [`schedLate()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L111) - manages the placement tables and installs the final control inputs; - [`breadth()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L126) - drives the worklist and wakes definitions and waiting loads as uses finish. -* [`_doSchedLate()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L172) - finds the common dominator of uses, accounts for load anti-dependencies, and - walks toward the early placement. It calls - [`better()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L213) - to compare candidate blocks. -* [`use_block()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L200) - handles Phi uses at their matching incoming control paths, rather than at - the merge itself. - -## Inserting Anti Dependencies - -To ensure that Loads and Stores to the same memory location are correctly -ordered, we insert an edge from the Store to the Load as described below: the -Store must wait for the earlier Load. We call these edges anti-dependencies -because they do not represent the Def-Use dependency that we normally capture -in SoN, and are purely present as scheduling constraints. - -We compute anti-dependencies DURING running schedule late. This is because we rely on the early-schedule, and the late-schedule of the Load's uses (before scheduling the Load). - -Looking backwards from the Load, we follow its memory input to Start's memory -projection, a memory Phi, or a Store. Optimization has already selected the -Load's precise slice through any MemMerge. We inspect users of that memory -definition, keeping only Stores and memory Phis that cover the Load's alias. -Start's single memory projection may supply many aliases, so sharing a memory -input alone does not imply an anti-dependency. - -MemMerge only packages slices; it does not overwrite any of them. Treating it -as a write could make a Load wait for an aggregate which itself depends on the -Load's result. The same alias filter is used both when deciding whether a Load -is ready to schedule and when computing its anti-dependencies. A completed -memory user wakes waiting loads even if their shared memory definition has -already been scheduled. - -Since we're in the middle of `schedLate`, we have already computed all the late -schedules of a Load's users, and we have the Loads *Least Common Ancestor* of -uses, the LCA or late position. We inspect the set of mem-defs that might -impact the Load, and either add an anti-dependency from Store to Load, or raise -the Loads effective LCA. For Phi mem-defs, we look at the Phi inputs and place -an effective use on that block; this will be used to raise the Load's LCA. For -stores, we do the same - until/unless we find stores with the SAME block as the -Load's LCA. Then we add the anti-dependency edge to force ordering within the -same block. - -See [`antiUse()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L222) -for the alias filter, and -[`find_anti_dep()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L231) -for marking the Load's candidate blocks and inspecting competing memory users. -[`anti_dep()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L257) -walks the competing operation's dominator interval, raises the Load's LCA, -and adds a Store-to-Load edge when required. It checks the Store's full interval -because the evaluator's scheduler may place that Store independently of GCM. - -## Video Walk Through - -Please watch the following video for a detailed walk through of the implementation. - -* [Coffee Compiler Club - 21 June 2024](https://youtu.be/5Po0gxfE7LA?feature=shared). -* [Coffee Compiler Club - 26 June 2024](https://www.youtube.com/watch?v=HnN2YzmFb-8&t=0s) - -[^1]: Cliff Click. (1995). - Global Code Motion Global Value Numbering. +Next, [Chapter 12](../chapter12/README.md) adds reference fields and recursive +structs while retaining this memory representation. diff --git a/chapter11/chapter11.iml b/chapter11/chapter11.iml new file mode 100644 index 00000000..92b9e5ca --- /dev/null +++ b/chapter11/chapter11.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chapter11/docs/10-grammar.md b/chapter11/docs/10-grammar.md new file mode 100644 index 00000000..5a3f3778 --- /dev/null +++ b/chapter11/docs/10-grammar.md @@ -0,0 +1,125 @@ +# Grammar for Chapter 11 + +```antlrv4 +grammar SimpleLanguage; + +program + : statement+ EOF + ; + +statement + : returnStatement + | structDeclaration + | declStatement + | blockStatment + | expressionStatement + | ifStatement + | whileStatement + | breakStatement + | continueStatment + ; + +field + : 'int' IDENTIFIER ';' + ; + +fields + : field+ + ; + +structDeclaration + : 'struct' IDENTIFIER '{' fields '}' + ; + +whileStatement + : 'while' '(' expression ')' statement + ; + +breakStatement + : 'break' ';' + ; + +continueStatement + : 'continue' ';' + ; + +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + + +expressionStatement + : IDENTIFIER '=' expression ';' + | fieldExpression '=' expression ';' + ; + +blockStatement + : '{' statement+ '}' + ; + +structName + : IDENTIFIER + ; + +declStatement + : 'int' IDENTIFIER '=' expression ';' + | structName ('?')? IDENTIFIER '=' expression ';' + ; + +returnStatement + : 'return' expression ';' + ; + +expression + : comparisonExpression + ; + +comparisonExpression + : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-') unaryExpression + | '!' unaryExpression + | primaryExpression + ; + +newExpression + : 'new' IDENTIFIER + ; + +fieldExpression + : primaryExpresson '.' IDENTIFIER + ; + +primaryExpression + : IDENTIFIER + | INTEGER_LITERAL + | 'true' + | 'false' + | 'null' + | newExpression + | '(' expression ')' + | fieldExpression + ; + +INTEGER_LITERAL + : [1-9][0-9]* + | [0] + ; + +IDENTIFIER + : NON_DIGIT (NON_DIGIT | DIGIT)* + ; + +NON_DIGIT: [a-zA-Z_]; +DEC_DIGIT: [0-9]; +``` diff --git a/chapter11/docs/11-grammar.md b/chapter11/docs/11-grammar.md deleted file mode 100644 index 5a3f3778..00000000 --- a/chapter11/docs/11-grammar.md +++ /dev/null @@ -1,125 +0,0 @@ -# Grammar for Chapter 11 - -```antlrv4 -grammar SimpleLanguage; - -program - : statement+ EOF - ; - -statement - : returnStatement - | structDeclaration - | declStatement - | blockStatment - | expressionStatement - | ifStatement - | whileStatement - | breakStatement - | continueStatment - ; - -field - : 'int' IDENTIFIER ';' - ; - -fields - : field+ - ; - -structDeclaration - : 'struct' IDENTIFIER '{' fields '}' - ; - -whileStatement - : 'while' '(' expression ')' statement - ; - -breakStatement - : 'break' ';' - ; - -continueStatement - : 'continue' ';' - ; - -ifStatement - : 'if' '(' expression ')' statement ('else' statement)? - ; - - -expressionStatement - : IDENTIFIER '=' expression ';' - | fieldExpression '=' expression ';' - ; - -blockStatement - : '{' statement+ '}' - ; - -structName - : IDENTIFIER - ; - -declStatement - : 'int' IDENTIFIER '=' expression ';' - | structName ('?')? IDENTIFIER '=' expression ';' - ; - -returnStatement - : 'return' expression ';' - ; - -expression - : comparisonExpression - ; - -comparisonExpression - : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* - ; - -additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* - ; - -multiplicativeExpression - : unaryExpression (('*' | '/') unaryExpression)* - ; - -unaryExpression - : ('-') unaryExpression - | '!' unaryExpression - | primaryExpression - ; - -newExpression - : 'new' IDENTIFIER - ; - -fieldExpression - : primaryExpresson '.' IDENTIFIER - ; - -primaryExpression - : IDENTIFIER - | INTEGER_LITERAL - | 'true' - | 'false' - | 'null' - | newExpression - | '(' expression ')' - | fieldExpression - ; - -INTEGER_LITERAL - : [1-9][0-9]* - | [0] - ; - -IDENTIFIER - : NON_DIGIT (NON_DIGIT | DIGIT)* - ; - -NON_DIGIT: [a-zA-Z_]; -DEC_DIGIT: [0-9]; -``` diff --git a/chapter11/docs/example1.gv b/chapter11/docs/example1.gv new file mode 100644 index 00000000..5e059d77 --- /dev/null +++ b/chapter11/docs/example1.gv @@ -0,0 +1,20 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=BT; + + M [label="Initial memory" shape=octagon style="filled" fillcolor="#dcecff"]; + X [label="Store x = 42" shape=octagon style="filled" fillcolor="#dcecff"]; + Y [label="Loop: Store y" shape=octagon style="filled" fillcolor="#dcecff"]; + L [label="Load x (42)" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + L -> X -> M [color="#2867b2"]; Y -> M [color="#2867b2"]; + MemMerge -> X [color="#2867b2"]; MemMerge -> Y [color="#2867b2"]; + Return -> MemMerge [color="#2867b2"]; Return -> L; + { rank=same; MemMerge -> L [style=invis]; } + Y -> X [style=invis]; // Layout only: keep the stores in code order. + Return [shape=box style="filled" fillcolor="#fff1c4"]; + MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; +} diff --git a/chapter11/docs/example1.json b/chapter11/docs/example1.json new file mode 100644 index 00000000..c0c28450 --- /dev/null +++ b/chapter11/docs/example1.json @@ -0,0 +1,31 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example1.svg", + "step": 1, + "roots": [24], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 7, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 11, "label": "MemPhi y", "type": "MEM#3", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 19, "role": "MEM", "label": null}], "proj": null}, + {"id": 12, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 15, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 16, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, + {"id": 17, "label": "Load p.y", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 18, "label": "+", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "DATA", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null}, + {"id": 19, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 18, "role": "DATA", "label": null}], "proj": null}, + {"id": 20, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 21, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "DATA", "label": null}, {"idx": 2, "def": 20, "role": "DATA", "label": null}], "proj": null}, + {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 8, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null}, + {"id": 23, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 16, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 24, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 23, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter11/docs/example1.svg b/chapter11/docs/example1.svg new file mode 100644 index 00000000..46faf818 --- /dev/null +++ b/chapter11/docs/example1.svg @@ -0,0 +1,155 @@ +#6[0] → #1 · ASSOC#7[0] → #1 · ASSOC#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #7 · DATA#9[1] → #3 · MEM#9[2] → #5 · DATA#9[3] → #6 · DATA#11[1] → #9 · MEM#11[2] → #19 · MEM#12[1] → #4 · DATA#12[2] → #21 · DATA#17[0] → #15 · CTRL#17[1] → #11 · MEM#17[2] → #5 · DATA#18[1] → #17 · DATA#18[2] → #12 · DATA#19[0] → #15 · CTRL#19[1] → #11 · MEM#19[2] → #5 · DATA#19[3] → #18 · DATA#20[0] → #1 · ASSOC#21[1] → #12 · DATA#21[2] → #20 · DATA#22[1] → #3 · MEM#22[2] → #8 · MEM#22[3] → #11 · MEM#23[0] → #16 · CTRL#23[1] → #8 · MEM#23[2] → #5 · DATA#24[1] → #22 · MEM#24[2] → #23 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0#42  #7#7 #42 +Type not computed + +[0] → #1 ASSOC0Store p.x  #8MEM#2#8 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #7 DATA0123Store p.y  #9MEM#3#9 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123MemPhi y  #11MEM#3#11 MemPhi y +MEM#3 + +[0] → — ASSOC +[1] → #9 MEM +[2] → #19 MEM012Phi arg  #12#12 Phi arg +Type not computed + +[0] → — ASSOC +[1] → #4 DATA +[2] → #21 DATA012True  #15/0#15 True +Projection 0 of #0 +Type not computed + +False  #16/1#16 False +Projection 1 of #0 +Type not computed + +Load p.y  #17#17 Load p.y +Type not computed + +[0] → #15 CTRL +[1] → #11 MEM +[2] → #5 DATA +[3] → — DATA0123+  #18#18 + +Type not computed + +[0] → — CTRL +[1] → #17 DATA +[2] → #12 DATA012Store p.y  #19MEM#3#19 Store p.y +MEM#3 + +[0] → #15 CTRL +[1] → #11 MEM +[2] → #5 DATA +[3] → #18 DATA0123#1  #20#20 #1 +Type not computed + +[0] → #1 ASSOC0-  #21#21 - +Type not computed + +[0] → — CTRL +[1] → #12 DATA +[2] → #20 DATA012MemMerge  #22#22 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #8 MEM +[3] → #11 MEM0123Load p.x  #23#23 Load p.x +Type not computed + +[0] → #16 CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → — DATA0123Return  #24#24 Return +Type not computed + +[0] → — CTRL +[1] → #22 MEM +[2] → #23 DATA012 diff --git a/chapter11/docs/example2a.gv b/chapter11/docs/example2a.gv new file mode 100644 index 00000000..187e63b9 --- /dev/null +++ b/chapter11/docs/example2a.gv @@ -0,0 +1,17 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + M [label="Default memory" shape=octagon style="filled" fillcolor="#dcecff"]; + X [label="Store x" shape=octagon style="filled" fillcolor="#dcecff"]; + Y [label="Store y" shape=octagon style="filled" fillcolor="#dcecff"]; + X -> M [color="#2867b2"]; Y -> M [color="#2867b2"]; + MemMerge -> M [xlabel="default",color="#2867b2"]; MemMerge -> X [xlabel="x",color="#2867b2"]; + MemMerge -> Y [xlabel="y",color="#2867b2"]; Return -> MemMerge [color="#2867b2"]; + Return [shape=box style="filled" fillcolor="#fff1c4"]; + MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; +} diff --git a/chapter11/docs/example2a.json b/chapter11/docs/example2a.json new file mode 100644 index 00000000..b9e84d5a --- /dev/null +++ b/chapter11/docs/example2a.json @@ -0,0 +1,20 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2a.svg", + "step": 1, + "roots": [11], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 9, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 8, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 9, "role": "MEM", "label": null}], "proj": null}, + {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter11/docs/example2a.svg b/chapter11/docs/example2a.svg new file mode 100644 index 00000000..b43a55b4 --- /dev/null +++ b/chapter11/docs/example2a.svg @@ -0,0 +1,103 @@ +#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #4 · DATA#8[0] → #1 · ASSOC#9[1] → #3 · MEM#9[2] → #5 · DATA#9[3] → #8 · DATA#10[1] → #3 · MEM#10[2] → #7 · MEM#10[3] → #9 · MEM#11[1] → #10 · MEM#11[2] → #5 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0Store p.x  #7MEM#2#7 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #4 DATA0123#42  #8#8 #42 +Type not computed + +[0] → #1 ASSOC0Store p.y  #9MEM#3#9 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #8 DATA0123MemMerge  #10#10 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #7 MEM +[3] → #9 MEM0123Return  #11#11 Return +Type not computed + +[0] → — CTRL +[1] → #10 MEM +[2] → #5 DATA012 diff --git a/chapter11/docs/example2b-final.json b/chapter11/docs/example2b-final.json new file mode 100644 index 00000000..b0a7a2f3 --- /dev/null +++ b/chapter11/docs/example2b-final.json @@ -0,0 +1,251 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2b-final.svg", + "step": 100, + "roots": [ + 23 + ], + "scope": 0, + "nodes": [ + { + "id": 1, + "label": "Start", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": null + }, + { + "id": 3, + "label": "$mem", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "MEM", + "label": null + } + ], + "proj": { + "par": 1, + "idx": 1 + } + }, + { + "id": 5, + "label": "new Point", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + } + ], + "proj": null + }, + { + "id": 6, + "label": "#0", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 8, + "label": "Store p.y", + "type": "MEM#3", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 6, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 13, + "label": "#3", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 15, + "label": "#4", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 22, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 27, + "role": "MEM", + "label": null + }, + { + "idx": 3, + "def": 8, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 23, + "label": "Return", + "type": null, + "kind": "CTRL", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 22, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 27, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 28, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 28, + "label": "Phi x value", + "type": null, + "kind": "PHI", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "ASSOC", + "label": null + }, + { + "idx": 1, + "def": 13, + "role": "DATA", + "label": null + }, + { + "idx": 2, + "def": 15, + "role": "DATA", + "label": null + } + ], + "proj": null + } + ], + "groups": [] +} diff --git a/chapter11/docs/example2b-final.svg b/chapter11/docs/example2b-final.svg new file mode 100644 index 00000000..163d7fce --- /dev/null +++ b/chapter11/docs/example2b-final.svg @@ -0,0 +1,110 @@ +#6[0] → #1 · ASSOC#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#13[0] → #1 · ASSOC#15[0] → #1 · ASSOC#22[1] → #3 · MEM#22[2] → #27 · MEM#22[3] → #8 · MEM#23[1] → #22 · MEM#23[2] → #5 · DATA#27[1] → #3 · MEM#27[2] → #5 · DATA#27[3] → #28 · DATA#28[1] → #13 · DATA#28[2] → #15 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.y  #8MEM#3#8 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123#3  #13#13 #3 +Type not computed + +[0] → #1 ASSOC0#4  #15#15 #4 +Type not computed + +[0] → #1 ASSOC0MemMerge  #22#22 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #27 MEM +[3] → #8 MEM0123Return  #23#23 Return +Type not computed + +[0] → — CTRL +[1] → #22 MEM +[2] → #5 DATA012Store p.x  #27MEM#2#27 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #28 DATA0123Phi x value  #28#28 Phi x value +Type not computed + +[0] → — ASSOC +[1] → #13 DATA +[2] → #15 DATA012 diff --git a/chapter11/docs/example2b-merged.json b/chapter11/docs/example2b-merged.json new file mode 100644 index 00000000..914b7cfb --- /dev/null +++ b/chapter11/docs/example2b-merged.json @@ -0,0 +1,339 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2b-merged.svg", + "step": 8, + "roots": [ + 23 + ], + "scope": 0, + "nodes": [ + { + "id": 1, + "label": "Start", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": null + }, + { + "id": 3, + "label": "$mem", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "MEM", + "label": null + } + ], + "proj": { + "par": 1, + "idx": 1 + } + }, + { + "id": 5, + "label": "new Point", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + } + ], + "proj": null + }, + { + "id": 6, + "label": "#0", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 7, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 6, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 8, + "label": "Store p.y", + "type": "MEM#3", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 6, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 11, + "label": "True", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": { + "par": 0, + "idx": 0 + } + }, + { + "id": 12, + "label": "False", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": { + "par": 0, + "idx": 1 + } + }, + { + "id": 13, + "label": "#3", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 14, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 11, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 7, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 13, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 15, + "label": "#4", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 16, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 12, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 7, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 15, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 20, + "label": "MemPhi x", + "type": "MEM#2", + "kind": "PHI", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "ASSOC", + "label": null + }, + { + "idx": 1, + "def": 14, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 16, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 22, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 20, + "role": "MEM", + "label": null + }, + { + "idx": 3, + "def": 8, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 23, + "label": "Return", + "type": null, + "kind": "CTRL", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 22, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + } + ], + "proj": null + } + ], + "groups": [] +} diff --git a/chapter11/docs/example2b-merged.svg b/chapter11/docs/example2b-merged.svg new file mode 100644 index 00000000..21ae615e --- /dev/null +++ b/chapter11/docs/example2b-merged.svg @@ -0,0 +1,130 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#13[0] → #1 · ASSOC#14[0] → #11 · CTRL#14[1] → #7 · MEM#14[2] → #5 · DATA#14[3] → #13 · DATA#15[0] → #1 · ASSOC#16[0] → #12 · CTRL#16[1] → #7 · MEM#16[2] → #5 · DATA#16[3] → #15 · DATA#20[1] → #14 · MEM#20[2] → #16 · MEM#22[1] → #3 · MEM#22[2] → #20 · MEM#22[3] → #8 · MEM#23[1] → #22 · MEM#23[2] → #5 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123True  #11/0#11 True +Projection 0 of #0 +Type not computed + +False  #12/1#12 False +Projection 1 of #0 +Type not computed + +#3  #13#13 #3 +Type not computed + +[0] → #1 ASSOC0Store p.x  #14MEM#2#14 Store p.x +MEM#2 + +[0] → #11 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #13 DATA0123#4  #15#15 #4 +Type not computed + +[0] → #1 ASSOC0Store p.x  #16MEM#2#16 Store p.x +MEM#2 + +[0] → #12 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #15 DATA0123MemPhi x  #20MEM#2#20 MemPhi x +MEM#2 + +[0] → — ASSOC +[1] → #14 MEM +[2] → #16 MEM012MemMerge  #22#22 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #20 MEM +[3] → #8 MEM0123Return  #23#23 Return +Type not computed + +[0] → — CTRL +[1] → #22 MEM +[2] → #5 DATA012 diff --git a/chapter11/docs/example2b-split-y.json b/chapter11/docs/example2b-split-y.json new file mode 100644 index 00000000..fc3c8758 --- /dev/null +++ b/chapter11/docs/example2b-split-y.json @@ -0,0 +1,486 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2b-split-y.svg", + "step": 3, + "roots": [ + 23 + ], + "scope": 0, + "nodes": [ + { + "id": 1, + "label": "Start", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": null + }, + { + "id": 3, + "label": "$mem", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "MEM", + "label": null + } + ], + "proj": { + "par": 1, + "idx": 1 + } + }, + { + "id": 5, + "label": "new Point", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + } + ], + "proj": null + }, + { + "id": 6, + "label": "#0", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 7, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 6, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 8, + "label": "Store p.y", + "type": "MEM#3", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 6, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 11, + "label": "True", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": { + "par": 0, + "idx": 0 + } + }, + { + "id": 12, + "label": "False", + "type": null, + "kind": "CTRL", + "edges": [], + "proj": { + "par": 0, + "idx": 1 + } + }, + { + "id": 13, + "label": "#3", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 14, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 11, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 7, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 13, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 15, + "label": "#4", + "type": null, + "kind": "DATA", + "edges": [ + { + "idx": 0, + "def": 1, + "role": "ASSOC", + "label": null + } + ], + "proj": null + }, + { + "id": 16, + "label": "Store p.x", + "type": "MEM#2", + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 12, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 7, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + }, + { + "idx": 3, + "def": 15, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 17, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 14, + "role": "MEM", + "label": null + }, + { + "idx": 3, + "def": 8, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 18, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 3, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 16, + "role": "MEM", + "label": null + }, + { + "idx": 3, + "def": 8, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 20, + "label": "MemPhi x", + "type": "MEM#2", + "kind": "PHI", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "ASSOC", + "label": null + }, + { + "idx": 1, + "def": 14, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 16, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 22, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 26, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 20, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 23, + "label": "Return", + "type": null, + "kind": "CTRL", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 22, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 5, + "role": "DATA", + "label": null + } + ], + "proj": null + }, + { + "id": 24, + "label": "MemPhi y", + "type": "MEM#3", + "kind": "PHI", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "ASSOC", + "label": null + }, + { + "idx": 1, + "def": 8, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 8, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 25, + "label": "BulkMemPhi except x,y", + "type": null, + "kind": "PHI", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "ASSOC", + "label": null + }, + { + "idx": 1, + "def": 17, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 18, + "role": "MEM", + "label": null + } + ], + "proj": null + }, + { + "id": 26, + "label": "MemMerge", + "type": null, + "kind": "MEM", + "edges": [ + { + "idx": 0, + "def": 0, + "role": "CTRL", + "label": null + }, + { + "idx": 1, + "def": 25, + "role": "MEM", + "label": null + }, + { + "idx": 2, + "def": 20, + "role": "MEM", + "label": null + }, + { + "idx": 3, + "def": 24, + "role": "MEM", + "label": null + } + ], + "proj": null + } + ], + "groups": [] +} diff --git a/chapter11/docs/example2b-split-y.svg b/chapter11/docs/example2b-split-y.svg new file mode 100644 index 00000000..f4d4b9b6 --- /dev/null +++ b/chapter11/docs/example2b-split-y.svg @@ -0,0 +1,157 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#13[0] → #1 · ASSOC#14[0] → #11 · CTRL#14[1] → #7 · MEM#14[2] → #5 · DATA#14[3] → #13 · DATA#15[0] → #1 · ASSOC#16[0] → #12 · CTRL#16[1] → #7 · MEM#16[2] → #5 · DATA#16[3] → #15 · DATA#17[1] → #3 · MEM#17[2] → #14 · MEM#17[3] → #8 · MEM#18[1] → #3 · MEM#18[2] → #16 · MEM#18[3] → #8 · MEM#20[1] → #14 · MEM#20[2] → #16 · MEM#22[1] → #26 · MEM#22[2] → #20 · MEM#23[1] → #22 · MEM#23[2] → #5 · DATA#24[1] → #8 · MEM#24[2] → #8 · MEM#25[1] → #17 · MEM#25[2] → #18 · MEM#26[1] → #25 · MEM#26[2] → #20 · MEM#26[3] → #24 · MEMStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123True  #11/0#11 True +Projection 0 of #0 +Type not computed + +False  #12/1#12 False +Projection 1 of #0 +Type not computed + +#3  #13#13 #3 +Type not computed + +[0] → #1 ASSOC0Store p.x  #14MEM#2#14 Store p.x +MEM#2 + +[0] → #11 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #13 DATA0123#4  #15#15 #4 +Type not computed + +[0] → #1 ASSOC0Store p.x  #16MEM#2#16 Store p.x +MEM#2 + +[0] → #12 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #15 DATA0123MemMerge  #17#17 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #14 MEM +[3] → #8 MEM0123MemMerge  #18#18 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #16 MEM +[3] → #8 MEM0123MemPhi x  #20MEM#2#20 MemPhi x +MEM#2 + +[0] → — ASSOC +[1] → #14 MEM +[2] → #16 MEM012MemMerge  #22#22 MemMerge +Type not computed + +[0] → — CTRL +[1] → #26 MEM +[2] → #20 MEM012Return  #23#23 Return +Type not computed + +[0] → — CTRL +[1] → #22 MEM +[2] → #5 DATA012MemPhi y  #24MEM#3#24 MemPhi y +MEM#3 + +[0] → — ASSOC +[1] → #8 MEM +[2] → #8 MEM012BulkMemPhi except x,y  #25#25 BulkMemPhi except x,y +Type not computed + +[0] → — ASSOC +[1] → #17 MEM +[2] → #18 MEM012MemMerge  #26#26 MemMerge +Type not computed + +[0] → — CTRL +[1] → #25 MEM +[2] → #20 MEM +[3] → #24 MEM0123 diff --git a/chapter11/docs/example2b.gv b/chapter11/docs/example2b.gv new file mode 100644 index 00000000..39b87a6a --- /dev/null +++ b/chapter11/docs/example2b.gv @@ -0,0 +1,19 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + P [label="MemPhi(x)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; + B [label="BulkMemPhi(All except x)" shape=hexagon style="filled" fillcolor="#f5e4fa"]; + P -> Left [xlabel="slice x",color="#2867b2"]; P -> Right [xlabel="slice x",color="#2867b2"]; + B -> Left [color="#2867b2"]; B -> Right [color="#2867b2"]; + P -> Region [style=dashed,color="#9396a3"]; B -> Region [style=dashed,color="#9396a3"]; + MemMerge -> P [xlabel="x",color="#2867b2"]; MemMerge -> B [xlabel="default",color="#2867b2"]; + MemMerge [shape=octagon style="filled" fillcolor="#dcecff"]; + Left [shape=octagon style="filled" fillcolor="#dcecff"]; + Right [shape=octagon style="filled" fillcolor="#dcecff"]; + Region [shape=trapezium style="filled" fillcolor="#fff1c4"]; +} diff --git a/chapter11/docs/example2b.json b/chapter11/docs/example2b.json new file mode 100644 index 00000000..702773ea --- /dev/null +++ b/chapter11/docs/example2b.json @@ -0,0 +1,30 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2b.svg", + "step": 1, + "roots": [23], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 8, "role": "MEM", "label": null}], "proj": null}, + {"id": 11, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 12, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}}, + {"id": 13, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 14, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 11, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 13, "role": "DATA", "label": null}], "proj": null}, + {"id": 15, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 16, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 12, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 15, "role": "DATA", "label": null}], "proj": null}, + {"id": 17, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null}, + {"id": 18, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null}, + {"id": 20, "label": "MemPhi x", "type": "MEM#2", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null}, + {"id": 21, "label": "BulkMemPhi except x", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "MEM", "label": null}], "proj": null}, + {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 21, "role": "MEM", "label": null}, {"idx": 2, "def": 20, "role": "MEM", "label": null}], "proj": null}, + {"id": 23, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter11/docs/example2b.svg b/chapter11/docs/example2b.svg new file mode 100644 index 00000000..54ddad9d --- /dev/null +++ b/chapter11/docs/example2b.svg @@ -0,0 +1,150 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #3 · MEM#9[2] → #7 · MEM#9[3] → #8 · MEM#13[0] → #1 · ASSOC#14[0] → #11 · CTRL#14[1] → #7 · MEM#14[2] → #5 · DATA#14[3] → #13 · DATA#15[0] → #1 · ASSOC#16[0] → #12 · CTRL#16[1] → #7 · MEM#16[2] → #5 · DATA#16[3] → #15 · DATA#17[1] → #9 · MEM#17[2] → #14 · MEM#18[1] → #9 · MEM#18[2] → #16 · MEM#20[1] → #14 · MEM#20[2] → #16 · MEM#21[1] → #17 · MEM#21[2] → #18 · MEM#22[1] → #21 · MEM#22[2] → #20 · MEM#23[1] → #22 · MEM#23[2] → #5 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123MemMerge  #9#9 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #7 MEM +[3] → #8 MEM0123True  #11/0#11 True +Projection 0 of #0 +Type not computed + +False  #12/1#12 False +Projection 1 of #0 +Type not computed + +#3  #13#13 #3 +Type not computed + +[0] → #1 ASSOC0Store p.x  #14MEM#2#14 Store p.x +MEM#2 + +[0] → #11 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #13 DATA0123#4  #15#15 #4 +Type not computed + +[0] → #1 ASSOC0Store p.x  #16MEM#2#16 Store p.x +MEM#2 + +[0] → #12 CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #15 DATA0123MemMerge  #17#17 MemMerge +Type not computed + +[0] → — CTRL +[1] → #9 MEM +[2] → #14 MEM012MemMerge  #18#18 MemMerge +Type not computed + +[0] → — CTRL +[1] → #9 MEM +[2] → #16 MEM012MemPhi x  #20MEM#2#20 MemPhi x +MEM#2 + +[0] → — ASSOC +[1] → #14 MEM +[2] → #16 MEM012BulkMemPhi except x  #21#21 BulkMemPhi except x +Type not computed + +[0] → — ASSOC +[1] → #17 MEM +[2] → #18 MEM012MemMerge  #22#22 MemMerge +Type not computed + +[0] → — CTRL +[1] → #21 MEM +[2] → #20 MEM012Return  #23#23 Return +Type not computed + +[0] → — CTRL +[1] → #22 MEM +[2] → #5 DATA012 diff --git a/chapter11/docs/example2c.gv b/chapter11/docs/example2c.gv new file mode 100644 index 00000000..b32d186d --- /dev/null +++ b/chapter11/docs/example2c.gv @@ -0,0 +1,15 @@ +digraph Memory { + // IR styles match graph/web/render.js; arrows are use -> definition. + graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho]; + node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5]; + + rankdir=RL; + + M [label="Prior x memory" shape=octagon style="filled" fillcolor="#dcecff"]; + L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"]; + S [label="Store p.x" shape=octagon style="filled" fillcolor="#dcecff"]; + L -> M [color="#2867b2"]; S -> M [color="#2867b2"]; + S -> L [style=dashed,xlabel="execute load first"]; + Y [label="Store p.y: independent" shape=octagon style="filled" fillcolor="#dcecff"]; +} diff --git a/chapter11/docs/example2c.json b/chapter11/docs/example2c.json new file mode 100644 index 00000000..c25fa5b8 --- /dev/null +++ b/chapter11/docs/example2c.json @@ -0,0 +1,23 @@ +{ + "ver": 1, + "comp": "chapter11/docs/example2c.svg", + "step": 1, + "roots": [13], + "scope": 0, + "nodes": [ + {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}}, + {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}}, + {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null}, + {"id": 7, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 8, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null}, + {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 10, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 11, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 12, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 10, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null}, + {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter11/docs/example2c.svg b/chapter11/docs/example2c.svg new file mode 100644 index 00000000..01bbe10a --- /dev/null +++ b/chapter11/docs/example2c.svg @@ -0,0 +1,121 @@ +#6[0] → #1 · ASSOC#7[1] → #3 · MEM#7[2] → #5 · DATA#7[3] → #6 · DATA#8[1] → #3 · MEM#8[2] → #5 · DATA#8[3] → #6 · DATA#9[1] → #7 · MEM#9[2] → #5 · DATA#10[1] → #7 · MEM#10[2] → #5 · DATA#10[3] → #4 · DATA#11[1] → #8 · MEM#11[2] → #5 · DATA#11[3] → #4 · DATA#12[1] → #3 · MEM#12[2] → #10 · MEM#12[3] → #11 · MEM#13[1] → #12 · MEM#13[2] → #9 · DATAStart  #1#1 Start +Type not computed + +$mem  #3/1#3 $mem +Projection 1 of #1 +Type not computed + +[0] → #1 MEMarg  #4/2#4 arg +Projection 2 of #1 +Type not computed + +[0] → #1 DATAnew Point  #5#5 new Point +Type not computed + +[0] → — CTRL0#0  #6#6 #0 +Type not computed + +[0] → #1 ASSOC0Store p.x  #7MEM#2#7 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Store p.y  #8MEM#3#8 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #3 MEM +[2] → #5 DATA +[3] → #6 DATA0123Load p.x  #9#9 Load p.x +Type not computed + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → — DATA0123Store p.x  #10MEM#2#10 Store p.x +MEM#2 + +[0] → — CTRL +[1] → #7 MEM +[2] → #5 DATA +[3] → #4 DATA0123Store p.y  #11MEM#3#11 Store p.y +MEM#3 + +[0] → — CTRL +[1] → #8 MEM +[2] → #5 DATA +[3] → #4 DATA0123MemMerge  #12#12 MemMerge +Type not computed + +[0] → — CTRL +[1] → #3 MEM +[2] → #10 MEM +[3] → #11 MEM0123Return  #13#13 Return +Type not computed + +[0] → — CTRL +[1] → #12 MEM +[2] → #9 DATA012 diff --git a/chapter11/docs/graph1.gv b/chapter11/docs/graph1.gv deleted file mode 100644 index 3554a572..00000000 --- a/chapter11/docs/graph1.gv +++ /dev/null @@ -1,79 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Con_11 [ label="#2" ]; - Store12 [ label=".f =" ]; - new13 [ label="new S" ]; - Store14 [ label=".f =" ]; - Load16 [ label=".f" ]; - If17 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Con_21 [ label="#1" ]; - Store22 [ label=".f =" ]; - Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Region23;Phi_224;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return25[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store12 -> Start2:p1[taillabel=1 color=blue]; - Store12 -> new9[taillabel=2]; - Store12 -> Con_11[taillabel=3]; - new13 -> Start2:p0[taillabel=0 color=green]; - Store14 -> Store12[taillabel=1 color=blue]; - Store14 -> new13[taillabel=2]; - Store14 -> Con_4[taillabel=3]; - Load16 -> Store14[taillabel=1 color=blue]; - Load16 -> new9[taillabel=2]; - If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p2[taillabel=1]; - Store22 -> Store14[taillabel=1 color=blue]; - Store22 -> new9[taillabel=2]; - Store22 -> Con_21[taillabel=3]; - Region23 -> If17:p0[taillabel=1 color=red]; - Region23 -> If17:p1[taillabel=2 color=red]; - Phi_224 -> Region23 [style=dotted taillabel=0]; - Phi_224 -> Store22[taillabel=1 color=blue]; - Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; - Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=2]; - Return25 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_224[taillabel=2 color=blue]; -} diff --git a/chapter11/docs/graph1.json b/chapter11/docs/graph1.json deleted file mode 100644 index 2d58e0ba..00000000 --- a/chapter11/docs/graph1.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph1.svg", - "step": 1, - "roots": [25], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, - {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, - {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph1.svg b/chapter11/docs/graph1.svg deleted file mode 100644 index 93a2a03f..00000000 --- a/chapter11/docs/graph1.svg +++ /dev/null @@ -1,123 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — DATA -[1] → #6 MEM -[2] → #224 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMnew S  #9#9 new S -Type not computed - -[0] → — CTRL0#2  #11#11 #2 -Type not computed - -.f =  #12#12 .f = -Type not computed - -[0] → — DATA -[1] → #6 MEM -[2] → #9 DATA -[3] → #11 DATA0123new S  #13#13 new S -Type not computed - -[0] → — CTRL0.f =  #14#14 .f = -Type not computed - -[0] → — DATA -[1] → #12 MEM -[2] → #13 DATA -[3] → #4 DATA0123.f  #16#16 .f -Type not computed - -[0] → — DATA -[1] → #14 MEM -[2] → #9 DATA012#1  #21#21 #1 -Type not computed - -.f =  #22#22 .f = -Type not computed - -[0] → — DATA -[1] → #14 MEM -[2] → #9 DATA -[3] → #21 DATA0123Return  #25#25 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #22 MEM -[2] → #14 MEM012 diff --git a/chapter11/docs/graph2.gv b/chapter11/docs/graph2.gv deleted file mode 100644 index 730cf845..00000000 --- a/chapter11/docs/graph2.gv +++ /dev/null @@ -1,84 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Con_11 [ label="#2" ]; - Store12 [ label=".f =" ]; - new13 [ label="new S" ]; - Store14 [ label=".f =" ]; - Load16 [ label=".f" ]; - If17 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Con_21 [ label="#1" ]; - Store22 [ label=".f =" ]; - Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Region23;Phi_224;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return25[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store12 -> Start2:p0[taillabel=0 color=red]; - Store12 -> Start2:p1[taillabel=1 color=blue]; - Store12 -> new9[taillabel=2]; - Store12 -> Con_11[taillabel=3]; - new13 -> Start2:p0[taillabel=0 color=green]; - Store14 -> Start2:p0[taillabel=0 color=red]; - Store14 -> Store12[taillabel=1 color=blue]; - Store14 -> new13[taillabel=2]; - Store14 -> Con_4[taillabel=3]; - Load16 -> Start2:p0[taillabel=0 color=red]; - Load16 -> Store14[taillabel=1 color=blue]; - Load16 -> new9[taillabel=2]; - If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p2[taillabel=1]; - Store22 -> Start2:p0[taillabel=0 color=red]; - Store22 -> Store14[taillabel=1 color=blue]; - Store22 -> new9[taillabel=2]; - Store22 -> Con_21[taillabel=3]; - Region23 -> If17:p0[taillabel=1 color=red]; - Region23 -> If17:p1[taillabel=2 color=red]; - Phi_224 -> Region23 [style=dotted taillabel=0]; - Phi_224 -> Store22[taillabel=1 color=blue]; - Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; - Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=2]; - Return25 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_224[taillabel=2 color=blue]; - MemMerge -> Region23[taillabel=0 color=green]; -} diff --git a/chapter11/docs/graph2.json b/chapter11/docs/graph2.json deleted file mode 100644 index 602c1584..00000000 --- a/chapter11/docs/graph2.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph2.svg", - "step": 1, - "roots": [25], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, - {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, - {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph2.svg b/chapter11/docs/graph2.svg deleted file mode 100644 index 05210749..00000000 --- a/chapter11/docs/graph2.svg +++ /dev/null @@ -1,123 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #224 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMnew S  #9#9 new S -Type not computed - -[0] → — CTRL0#2  #11#11 #2 -Type not computed - -.f =  #12#12 .f = -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #9 DATA -[3] → #11 DATA0123new S  #13#13 new S -Type not computed - -[0] → — CTRL0.f =  #14#14 .f = -Type not computed - -[0] → — CTRL -[1] → #12 MEM -[2] → #13 DATA -[3] → #4 DATA0123.f  #16#16 .f -Type not computed - -[0] → — CTRL -[1] → #14 MEM -[2] → #9 DATA012#1  #21#21 #1 -Type not computed - -.f =  #22#22 .f = -Type not computed - -[0] → — CTRL -[1] → #14 MEM -[2] → #9 DATA -[3] → #21 DATA0123Return  #25#25 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #22 MEM -[2] → #14 MEM012 diff --git a/chapter11/docs/graph3-snip.jpg b/chapter11/docs/graph3-snip.jpg deleted file mode 100644 index 804a32c4..00000000 Binary files a/chapter11/docs/graph3-snip.jpg and /dev/null differ diff --git a/chapter11/docs/graph3-snip.json b/chapter11/docs/graph3-snip.json deleted file mode 100644 index 7dade538..00000000 --- a/chapter11/docs/graph3-snip.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph3-snip.svg", - "step": 1, - "roots": [22], - "scope": 0, - "nodes": [ - {"id": 8, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 0, "role": "MEM", "label": null}, {"idx": 2, "def": 0, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, - {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}, {"idx": 4, "def": 16, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph3-snip.svg b/chapter11/docs/graph3-snip.svg deleted file mode 100644 index 9f5218af..00000000 --- a/chapter11/docs/graph3-snip.svg +++ /dev/null @@ -1,91 +0,0 @@ -#16[1] → #14 · MEM#16[2] → #9 · DATA#22[0] → #8 · CTRL#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#22[4] → #16 · DATATrue  #8/0#8 True -Projection 0 of #0 -Type not computed - -new S  #9#9 new S -Type not computed - -[0] → — CTRL0.f =  #14#14 .f = -Type not computed - -[0] → — CTRL -[1] → — MEM -[2] → — DATA -[3] → — DATA0123.f  #16#16 .f -Type not computed - -[0] → — CTRL -[1] → #14 MEM -[2] → #9 DATA012#1  #21#21 #1 -Type not computed - -.f =  #22#22 .f = -Type not computed - -[0] → #8 CTRL -[1] → #14 MEM -[2] → #9 DATA -[3] → #21 DATA -[4] → #16 DATA01234 diff --git a/chapter11/docs/graph3.gv b/chapter11/docs/graph3.gv deleted file mode 100644 index d89d0ed4..00000000 --- a/chapter11/docs/graph3.gv +++ /dev/null @@ -1,85 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Con_11 [ label="#2" ]; - Store12 [ label=".f =" ]; - new13 [ label="new S" ]; - Store14 [ label=".f =" ]; - Load16 [ label=".f" ]; - If17 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Con_21 [ label="#1" ]; - Store22 [ label=".f =" ]; - Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Region23;Phi_224;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return25[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store12 -> Start2:p0[taillabel=0 color=red]; - Store12 -> Start2:p1[taillabel=1 color=blue]; - Store12 -> new9[taillabel=2]; - Store12 -> Con_11[taillabel=3]; - new13 -> Start2:p0[taillabel=0 color=green]; - Store14 -> Start2:p0[taillabel=0 color=red]; - Store14 -> Store12[taillabel=1 color=blue]; - Store14 -> new13[taillabel=2]; - Store14 -> Con_4[taillabel=3]; - Load16 -> Start2:p0[taillabel=0 color=red]; - Load16 -> Store14[taillabel=1 color=blue]; - Load16 -> new9[taillabel=2]; - If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p2[taillabel=1]; - Store22 -> If17:p0[taillabel=0 color=red]; - Store22 -> Store14[taillabel=1 color=blue]; - Store22 -> new9[taillabel=2]; - Store22 -> Con_21[taillabel=3]; - Store22 -> Load16[taillabel=4]; - Region23 -> If17:p0[taillabel=1 color=red]; - Region23 -> If17:p1[taillabel=2 color=red]; - Phi_224 -> Region23 [style=dotted taillabel=0]; - Phi_224 -> Store22[taillabel=1 color=blue]; - Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; - Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=2]; - Return25 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_224[taillabel=2 color=blue]; - MemMerge -> Region23[taillabel=0 color=green]; -} diff --git a/chapter11/docs/graph3.json b/chapter11/docs/graph3.json deleted file mode 100644 index 577d7c3b..00000000 --- a/chapter11/docs/graph3.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph3.svg", - "step": 1, - "roots": [25], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 8, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, - {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}, {"idx": 4, "def": 16, "role": "DATA", "label": null}], "proj": null}, - {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, - {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph3.svg b/chapter11/docs/graph3.svg deleted file mode 100644 index 0247b5c6..00000000 --- a/chapter11/docs/graph3.svg +++ /dev/null @@ -1,128 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[0] → #8 · CTRL#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#22[4] → #16 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #224 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMTrue  #8/0#8 True -Projection 0 of #0 -Type not computed - -new S  #9#9 new S -Type not computed - -[0] → — CTRL0#2  #11#11 #2 -Type not computed - -.f =  #12#12 .f = -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #9 DATA -[3] → #11 DATA0123new S  #13#13 new S -Type not computed - -[0] → — CTRL0.f =  #14#14 .f = -Type not computed - -[0] → — CTRL -[1] → #12 MEM -[2] → #13 DATA -[3] → #4 DATA0123.f  #16#16 .f -Type not computed - -[0] → — CTRL -[1] → #14 MEM -[2] → #9 DATA012#1  #21#21 #1 -Type not computed - -.f =  #22#22 .f = -Type not computed - -[0] → #8 CTRL -[1] → #14 MEM -[2] → #9 DATA -[3] → #21 DATA -[4] → #16 DATA01234Return  #25#25 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #22 MEM -[2] → #14 MEM012 diff --git a/chapter11/docs/graph4.gv b/chapter11/docs/graph4.gv deleted file mode 100644 index cf622aa1..00000000 --- a/chapter11/docs/graph4.gv +++ /dev/null @@ -1,110 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Store10 [ label=".f =" ]; - Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; - Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; - LT14 [ label="<" ]; - If16 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Div21 [ label="//" ]; - Con_22 [ label="#3" ]; - EQ23 [ label="==" ]; - Con_24 [ label="#5" ]; - If25 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Store31 [ label=".f =" ]; - Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Sub35 [ label="-" ]; - Con_36 [ label="#1" ]; - Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Loop11;Phi_230;Phi_arg13;} - { rank=same; Region32;Phi_233;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return37[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store10 -> Start2:p1[taillabel=1 color=blue]; - Store10 -> new9[taillabel=2]; - Store10 -> Con_4[taillabel=3]; - Loop11 -> Start2:p0[taillabel=1 color=red]; - Loop11 -> Region32[taillabel=2 color=red constraint=false]; - Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p2[taillabel=1]; - Phi_arg13 -> Sub35[taillabel=2 constraint=false]; - LT14 -> Con_4[taillabel=1]; - LT14 -> Phi_arg13[taillabel=2]; - If16 -> Loop11[taillabel=0 color=red]; - If16 -> LT14[taillabel=1]; - Div21 -> Start2:p2[taillabel=1]; - Div21 -> Con_22[taillabel=2]; - EQ23 -> Phi_arg13[taillabel=1]; - EQ23 -> Con_24[taillabel=2]; - If25 -> If16:p0[taillabel=0 color=red]; - If25 -> EQ23[taillabel=1]; - Phi_230 -> Loop11 [style=dotted taillabel=0]; - Phi_230 -> Store10[taillabel=1 color=blue]; - Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; - Store31 -> Phi_230[taillabel=1 color=blue]; - Store31 -> new9[taillabel=2]; - Store31 -> Div21[taillabel=3]; - Region32 -> If25:p0[taillabel=1 color=red]; - Region32 -> If25:p1[taillabel=2 color=red]; - Phi_233 -> Region32 [style=dotted taillabel=0]; - Phi_233 -> Store31[taillabel=1 color=blue]; - Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; - Sub35 -> Phi_arg13[taillabel=1]; - Sub35 -> Con_36[taillabel=2]; - Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=2]; - Return37 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_230[taillabel=2 color=blue]; -} diff --git a/chapter11/docs/graph4.json b/chapter11/docs/graph4.json deleted file mode 100644 index 44c1c055..00000000 --- a/chapter11/docs/graph4.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph4.svg", - "step": 1, - "roots": [37], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, - {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, - {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph4.svg b/chapter11/docs/graph4.svg deleted file mode 100644 index 9ca92879..00000000 --- a/chapter11/docs/graph4.svg +++ /dev/null @@ -1,120 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — DATA -[1] → #6 MEM -[2] → #230 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMarg  #7/2#7 arg -Projection 2 of #2 -Type not computed - -[0] → #2 DATAnew S  #9#9 new S -Type not computed - -[0] → — CTRL0.f =  #10#10 .f = -Type not computed - -[0] → — DATA -[1] → #6 MEM -[2] → #9 DATA -[3] → #4 DATA0123//  #21#21 // -Type not computed - -[0] → — DATA -[1] → #7 DATA -[2] → #22 DATA012#3  #22#22 #3 -Type not computed - -.f =  #31#31 .f = -Type not computed - -[0] → — DATA -[1] → #230 MEM -[2] → #9 DATA -[3] → #21 DATA0123Return  #37#37 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #10 MEM -[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #31 MEM -[2] → #230 MEM012 diff --git a/chapter11/docs/graph5.gv b/chapter11/docs/graph5.gv deleted file mode 100644 index 0baa512f..00000000 --- a/chapter11/docs/graph5.gv +++ /dev/null @@ -1,117 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Store10 [ label=".f =" ]; - Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; - Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; - LT14 [ label="<" ]; - If16 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Div21 [ label="//" ]; - Con_22 [ label="#3" ]; - EQ23 [ label="==" ]; - Con_24 [ label="#5" ]; - If25 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Store31 [ label=".f =" ]; - Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Sub35 [ label="-" ]; - Con_36 [ label="#1" ]; - Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Loop11;Phi_230;Phi_arg13;} - { rank=same; Region32;Phi_233;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return37[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store10 -> Start2:p0[taillabel=0 color=red]; - Store10 -> Start2:p1[taillabel=1 color=blue]; - Store10 -> new9[taillabel=2]; - Store10 -> Con_4[taillabel=3]; - Loop11 -> Start2:p0[taillabel=1 color=red]; - Loop11 -> Region32[taillabel=2 color=red constraint=false]; - Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p2[taillabel=1]; - Phi_arg13 -> Sub35[taillabel=2 constraint=false]; - LT14 -> Loop11[taillabel=0 color=red]; - LT14 -> Con_4[taillabel=1]; - LT14 -> Phi_arg13[taillabel=2]; - If16 -> Loop11[taillabel=0 color=red]; - If16 -> LT14[taillabel=1]; - Div21 -> Start2[taillabel=0 color=red]; - Div21 -> Start2:p2[taillabel=1]; - Div21 -> Con_22[taillabel=2]; - EQ23 -> Loop11[taillabel=0 color=red]; - EQ23 -> Phi_arg13[taillabel=1]; - EQ23 -> Con_24[taillabel=2]; - If25 -> If16:p0[taillabel=0 color=red]; - If25 -> EQ23[taillabel=1]; - Phi_230 -> Loop11 [style=dotted taillabel=0]; - Phi_230 -> Store10[taillabel=1 color=blue]; - Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; - Store31 -> Loop11[taillabel=0 color=red]; - Store31 -> Phi_230[taillabel=1 color=blue]; - Store31 -> new9[taillabel=2]; - Store31 -> Div21[taillabel=3]; - Region32 -> If25:p0[taillabel=1 color=red]; - Region32 -> If25:p1[taillabel=2 color=red]; - Phi_233 -> Region32 [style=dotted taillabel=0]; - Phi_233 -> Store31[taillabel=1 color=blue]; - Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; - Sub35 -> Loop11[taillabel=0 color=red]; - Sub35 -> Phi_arg13[taillabel=1]; - Sub35 -> Con_36[taillabel=2]; - Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=2]; - Return37 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_230[taillabel=2 color=blue]; - MemMerge -> Loop11[taillabel=0 color=green]; -} diff --git a/chapter11/docs/graph5.json b/chapter11/docs/graph5.json deleted file mode 100644 index 659384e0..00000000 --- a/chapter11/docs/graph5.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph5.svg", - "step": 1, - "roots": [37], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, - {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, - {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph5.svg b/chapter11/docs/graph5.svg deleted file mode 100644 index c120550f..00000000 --- a/chapter11/docs/graph5.svg +++ /dev/null @@ -1,120 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #230 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMarg  #7/2#7 arg -Projection 2 of #2 -Type not computed - -[0] → #2 DATAnew S  #9#9 new S -Type not computed - -[0] → — CTRL0.f =  #10#10 .f = -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #9 DATA -[3] → #4 DATA0123//  #21#21 // -Type not computed - -[0] → — CTRL -[1] → #7 DATA -[2] → #22 DATA012#3  #22#22 #3 -Type not computed - -.f =  #31#31 .f = -Type not computed - -[0] → — CTRL -[1] → #230 MEM -[2] → #9 DATA -[3] → #21 DATA0123Return  #37#37 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #10 MEM -[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #31 MEM -[2] → #230 MEM012 diff --git a/chapter11/docs/graph6.gv b/chapter11/docs/graph6.gv deleted file mode 100644 index ff97eebc..00000000 --- a/chapter11/docs/graph6.gv +++ /dev/null @@ -1,117 +0,0 @@ -digraph chapter11 { -/* -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl$memarg
-
> - ]; - MemMerge [ shape=box label="MemMerge" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - new9 [ label="new S" ]; - Store10 [ label=".f =" ]; - Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; - Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; - LT14 [ label="<" ]; - If16 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Div21 [ label="//" ]; - Con_22 [ label="#3" ]; - EQ23 [ label="==" ]; - Con_24 [ label="#5" ]; - If25 [ shape=plaintext label=< - - - -
If
- - -
TrueFalse
-
> - ]; - Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Store31 [ label=".f =" ]; - Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; - Sub35 [ label="-" ]; - Con_36 [ label="#1" ]; - Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Loop11;Phi_230;Phi_arg13;} - { rank=same; Region32;Phi_233;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return37[taillabel=0 color=red]; - new9 -> Start2:p0[taillabel=0 color=green]; - Store10 -> Start2:p0[taillabel=0 color=red]; - Store10 -> Start2:p1[taillabel=1 color=blue]; - Store10 -> new9[taillabel=2]; - Store10 -> Con_4[taillabel=3]; - Loop11 -> Start2:p0[taillabel=1 color=red]; - Loop11 -> Region32[taillabel=2 color=red constraint=false]; - Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p2[taillabel=1]; - Phi_arg13 -> Sub35[taillabel=2 constraint=false]; - LT14 -> Loop11[taillabel=0 color=red]; - LT14 -> Con_4[taillabel=1]; - LT14 -> Phi_arg13[taillabel=2]; - If16 -> Loop11[taillabel=0 color=red]; - If16 -> LT14[taillabel=1]; - Div21 -> Start2:p0[taillabel=0 color=red]; - Div21 -> Start2:p2[taillabel=1]; - Div21 -> Con_22[taillabel=2]; - EQ23 -> If16:p0[taillabel=0 color=red]; - EQ23 -> Phi_arg13[taillabel=1]; - EQ23 -> Con_24[taillabel=2]; - If25 -> If16:p0[taillabel=0 color=red]; - If25 -> EQ23[taillabel=1]; - Phi_230 -> Loop11 [style=dotted taillabel=0]; - Phi_230 -> Store10[taillabel=1 color=blue]; - Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; - Store31 -> If25:p0[taillabel=0 color=red]; - Store31 -> Phi_230[taillabel=1 color=blue]; - Store31 -> new9[taillabel=2]; - Store31 -> Div21[taillabel=3]; - Region32 -> If25:p0[taillabel=1 color=red]; - Region32 -> If25:p1[taillabel=2 color=red]; - Phi_233 -> Region32 [style=dotted taillabel=0]; - Phi_233 -> Store31[taillabel=1 color=blue]; - Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; - Sub35 -> Region32[taillabel=0 color=red]; - Sub35 -> Phi_arg13[taillabel=1]; - Sub35 -> Con_36[taillabel=2]; - Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=2]; - Return37 -> MemMerge[taillabel=1 color=blue]; - MemMerge -> Start2:p1[taillabel=1 color=blue]; - MemMerge -> Phi_230[taillabel=2 color=blue]; - MemMerge -> If16:p1[taillabel=0 color=green]; -} diff --git a/chapter11/docs/graph6.json b/chapter11/docs/graph6.json deleted file mode 100644 index 512c308d..00000000 --- a/chapter11/docs/graph6.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph6.svg", - "step": 1, - "roots": [37], - "scope": 0, - "nodes": [ - {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, - {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, - {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, - {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 15, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, - {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, - {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, - {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, - {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, - {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} - ], - "groups": [ - ] -} diff --git a/chapter11/docs/graph6.svg b/chapter11/docs/graph6.svg deleted file mode 100644 index d6dec88f..00000000 --- a/chapter11/docs/graph6.svg +++ /dev/null @@ -1,124 +0,0 @@ -#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[0] → #15 · CTRL#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #230 MEM012Start  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -$mem  #6/1#6 $mem -Projection 1 of #2 -Type not computed - -[0] → #2 MEMarg  #7/2#7 arg -Projection 2 of #2 -Type not computed - -[0] → #2 DATAnew S  #9#9 new S -Type not computed - -[0] → — CTRL0.f =  #10#10 .f = -Type not computed - -[0] → — CTRL -[1] → #6 MEM -[2] → #9 DATA -[3] → #4 DATA0123True  #15/0#15 True -Projection 0 of #0 -Type not computed - -//  #21#21 // -Type not computed - -[0] → — CTRL -[1] → #7 DATA -[2] → #22 DATA012#3  #22#22 #3 -Type not computed - -.f =  #31#31 .f = -Type not computed - -[0] → #15 CTRL -[1] → #230 MEM -[2] → #9 DATA -[3] → #21 DATA0123Return  #37#37 Return -Type not computed - -[0] → — CTRL -[1] → #1 MEM -[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #10 MEM -[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 -Type not computed - -[0] → — ASSOC -[1] → #31 MEM -[2] → #230 MEM012 diff --git a/chapter11/docs/graph7.gv b/chapter11/docs/graph7.gv deleted file mode 100644 index 18be364b..00000000 --- a/chapter11/docs/graph7.gv +++ /dev/null @@ -1,23 +0,0 @@ -digraph chapter11 { - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl
-
> - ]; - Con_4 [ label="#0" ]; - Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; - { rank=same; Loop8;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Loop8 -> Start2:p0[taillabel=1 color=red]; - Loop8 -> Loop8[taillabel=2 color=red constraint=false]; -} diff --git a/chapter11/docs/graph7.json b/chapter11/docs/graph7.json deleted file mode 100644 index c1c86b07..00000000 --- a/chapter11/docs/graph7.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph7.svg", - "step": 1, - "roots": [], - "scope": 0, - "nodes": [ - {"id": 1, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": {"par": 2, "idx": 0}}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 8, "label": "Loop", "type": null, "kind": "LOOP", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 1, "role": "CTRL", "label": null}, {"idx": 2, "def": 8, "role": "CTRL", "label": null}], "proj": null} - ], - "groups": [ - {"id": 8, "par": 0, "nodes": [8]} - ] -} diff --git a/chapter11/docs/graph7.svg b/chapter11/docs/graph7.svg deleted file mode 100644 index 15d886dc..00000000 --- a/chapter11/docs/graph7.svg +++ /dev/null @@ -1,78 +0,0 @@ -#8[1] → #1 · CTRL#8[2] → #8 · CTRL$ctrl  #1/0#1 $ctrl -Projection 0 of #2 -Type not computed - -[0] → #2 CTRLStart  #2#2 Start -Type not computed - -#0  #4#4 #0 -Type not computed - -Loop  #8#8 Loop -Type not computed - -[0] → — DATA -[1] → #1 CTRL -[2] → #8 CTRL012Loop  #8Loop · 1 nodes−Fold region diff --git a/chapter11/docs/graph8.gv b/chapter11/docs/graph8.gv deleted file mode 100644 index 431cef46..00000000 --- a/chapter11/docs/graph8.gv +++ /dev/null @@ -1,45 +0,0 @@ -digraph chapter11 { -/* -while (1) {} -return 0; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Never18 [ shape=plaintext label=< - - - -
Never
- - -
TrueFalse
-
> - ]; - Start2 [ shape=plaintext label=< - - - -
Start
- - -
$ctrl
-
> - ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - Con_4 [ label="#0" ]; - Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; - Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; - { rank=same; Loop8;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Never18 -> Loop8[taillabel=0 color=red]; - Never18 -> Con_4[taillabel=1]; - Stop3 -> Return21[taillabel=0 color=red]; - Return21 -> Never18:p0[taillabel=0 color=red]; - Return21 -> Con_4[taillabel=2]; - Loop8 -> Start2:p0[taillabel=1 color=red]; - Loop8 -> Never18:p1[taillabel=2 color=red constraint=false]; -} diff --git a/chapter11/docs/graph8.json b/chapter11/docs/graph8.json deleted file mode 100644 index ab2ef65c..00000000 --- a/chapter11/docs/graph8.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ver": 1, - "comp": "chapter11/docs/graph8.svg", - "step": 1, - "roots": [], - "scope": 0, - "nodes": [ - {"id": 1, "label": "True", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 18, "role": "CTRL", "label": null}], "proj": {"par": 18, "idx": 0}}, - {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, - {"id": 3, "label": "Stop", "type": null, "kind": "STOP", "edges": [{"idx": 0, "def": 21, "role": "CTRL", "label": null}], "proj": null}, - {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, - {"id": 5, "label": "False", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 18, "role": "CTRL", "label": null}], "proj": {"par": 18, "idx": 1}}, - {"id": 6, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": {"par": 2, "idx": 0}}, - {"id": 8, "label": "Loop", "type": null, "kind": "LOOP", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "CTRL", "label": null}, {"idx": 2, "def": 5, "role": "CTRL", "label": null}], "proj": null}, - {"id": 18, "label": "Never", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}], "proj": null}, - {"id": 21, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}, {"idx": 1, "def": 0, "role": "DATA", "label": null}, {"idx": 2, "def": 4, "role": "DATA", "label": null}], "proj": null} - ], - "groups": [ - {"id": 8, "par": 0, "nodes": [1, 5, 8, 18]} - ] -} diff --git a/chapter11/docs/graph8.svg b/chapter11/docs/graph8.svg deleted file mode 100644 index d96a9a7d..00000000 --- a/chapter11/docs/graph8.svg +++ /dev/null @@ -1,98 +0,0 @@ -#3[0] → #21 · CTRL#8[1] → #6 · CTRL#8[2] → #5 · CTRL#18[0] → #8 · CTRL#18[1] → #4 · DATA#21[0] → #1 · CTRL#21[2] → #4 · DATATrue  #1/0#1 True -Projection 0 of #18 -Type not computed - -[0] → #18 CTRLStart  #2#2 Start -Type not computed - -Stop  #3#3 Stop -Type not computed - -[0] → #21 CTRL0#0  #4#4 #0 -Type not computed - -False  #5/1#5 False -Projection 1 of #18 -Type not computed - -[0] → #18 CTRL$ctrl  #6/0#6 $ctrl -Projection 0 of #2 -Type not computed - -[0] → #2 CTRLLoop  #8#8 Loop -Type not computed - -[0] → — DATA -[1] → #6 CTRL -[2] → #5 CTRL012Never  #18#18 Never -Type not computed - -[0] → #8 CTRL -[1] → #4 DATA01Return  #21#21 Return -Type not computed - -[0] → #1 CTRL -[1] → — DATA -[2] → #4 DATA012Loop  #8Loop · 4 nodes−Fold region diff --git a/chapter11/docs/lattice.gv b/chapter11/docs/lattice.gv new file mode 100644 index 00000000..93d99dd8 --- /dev/null +++ b/chapter11/docs/lattice.gv @@ -0,0 +1,133 @@ +digraph Lattice { + rankdir=TB; + newrank=true; + // Preserve the same-rank ordering edges through cluster layout. + remincross=false; + nodesep=0.22; + ranksep=0.65; + node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; + edge [color="#555555", arrowsize=0.6]; + + // These arrows are lattice-order edges, not use/definition edges. + TOP [label="⊤", fillcolor=black, fontcolor=white]; + BOT [label="⊥", fillcolor=black, fontcolor=white]; + + subgraph cluster_control { + label="Control"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffff00"]; + XCTRL [label="⊤:ctrl"]; + CTRL [label="⊥:ctrl"]; + XCTRL -> CTRL; + } + + subgraph cluster_integer { + label="Integer"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#add8e6"]; + INT_TOP [label="⊤:int"]; + INT_BOT [label="⊥:int"]; + INT_NEG [label="… −2, −1"]; + INT_ZERO [label="0"]; + INT_POS [label="1, 2, …"]; + {rank=same; INT_NEG; INT_ZERO; INT_POS;} + INT_NEG -> INT_ZERO -> INT_POS [style=invis]; + INT_TOP -> {INT_NEG INT_ZERO INT_POS}; + {INT_NEG INT_ZERO INT_POS} -> INT_BOT; + } + + subgraph cluster_pointer { + label="Pointer = nullability × struct type"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#008000", fontcolor=white]; + PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; + PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; + PTR_NULL [label="null"]; + PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; + PTR_PERSON [label="*Person"]; + PTR_S1 [label="*s1"]; + PTR_MORE [label="*s2, …"]; + PTR_PERSON_NIL [label="*Person?"]; + PTR_S1_NIL [label="*s1?"]; + PTR_MORE_NIL [label="*s2?, …"]; + {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} + {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} + PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; + PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; + PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; + {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; + PTR_PERSON -> PTR_PERSON_NIL; + PTR_S1 -> PTR_S1_NIL; + PTR_MORE -> PTR_MORE_NIL; + PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; + {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; + } + + subgraph cluster_struct { + label="Struct\nnamed collection of named scalar types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#90ee90"]; + STRUCT_TOP [label="⊤:struct\n[struct top]"]; + STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; + STRUCT_PERSON [label="Person\nage: int", shape=box]; + STRUCT_S1 [label="s1\nx: int", shape=box]; + STRUCT_S2 [label="s2\nx: int, y: int", shape=box]; + STRUCT_MORE [label="…", shape=box]; + {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} + STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; + STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; + {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; + } + + subgraph cluster_memory { + label="Memory\nprecise alias slices"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#00008b", fontcolor=white]; + MEM_TOP [label="⊤:mem"]; + MEM_BOT [label="⊥:mem"]; + MEM_ALIAS2 [label="MEM#2"]; + MEM_ALIAS3 [label="MEM#3"]; + MEM_MORE [label="…"]; + {rank=same; MEM_ALIAS2; MEM_ALIAS3; MEM_MORE;} + MEM_ALIAS2 -> MEM_ALIAS3 -> MEM_MORE [style=invis]; + MEM_TOP -> {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE}; + {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE} -> MEM_BOT; + } + + subgraph cluster_tuple { + label="Tuple\ncollection of types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffc0cb"]; + TUPLE_TOP [label="⊤:tuple"]; + TUPLE_BOT [label="⊥:tuple"]; + // Each black element stands for a recursively nested type lattice. + TUPLE1 [shape=plain, label=< + + + +
tuple
type
+ >]; + TUPLE2 [shape=plain, label=< + + + + +
tuple
type
type
+ >]; + TUPLEN [shape=plain, label=< + + + + + +
tuple
type
…
type
+ >]; + {rank=same; TUPLE1; TUPLE2; TUPLEN;} + TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; + TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; + {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; + } + + TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; + {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; + {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} + {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} + XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; +} diff --git a/chapter11/docs/lattice.svg b/chapter11/docs/lattice.svg new file mode 100644 index 00000000..8d2e2875 --- /dev/null +++ b/chapter11/docs/lattice.svg @@ -0,0 +1,644 @@ + + + + + + +Lattice + + +cluster_control + +Control + + +cluster_integer + +Integer + + +cluster_pointer + +Pointer = nullability × struct type + + +cluster_struct + +Struct +named collection of named scalar types + + +cluster_memory + +Memory +precise alias slices + + +cluster_tuple + +Tuple +collection of types + + + +TOP + +⊤ + + + +XCTRL + +⊤:ctrl + + + +TOP->XCTRL + + + + + +INT_TOP + +⊤:int + + + +TOP->INT_TOP + + + + + +PTR_TOP + +⊤:ptr +[*⊤:struct] + + + +TOP->PTR_TOP + + + + + +STRUCT_TOP + +⊤:struct +[struct top] + + + +TOP->STRUCT_TOP + + + + + +MEM_TOP + +⊤:mem + + + +TOP->MEM_TOP + + + + + +TUPLE_TOP + +⊤:tuple + + + +TOP->TUPLE_TOP + + + + + +BOT + +⊥ + + + +CTRL + +⊥:ctrl + + + +XCTRL->CTRL + + + + + + +CTRL->BOT + + + + + +INT_NEG + +… −2, −1 + + + +INT_TOP->INT_NEG + + + + + +INT_ZERO + +0 + + + +INT_TOP->INT_ZERO + + + + + +INT_POS + +1, 2, … + + + +INT_TOP->INT_POS + + + + + + +INT_BOT + +⊥:int + + + +INT_BOT->BOT + + + + + +INT_NEG->INT_BOT + + + + + + +INT_ZERO->INT_BOT + + + + + + +INT_POS->INT_BOT + + + + + +PTR_NULL + +null + + + +PTR_TOP->PTR_NULL + + + + + +PTR_PERSON + +*Person + + + +PTR_TOP->PTR_PERSON + + + + + +PTR_S1 + +*s1 + + + +PTR_TOP->PTR_S1 + + + + + +PTR_MORE + +*s2, … + + + +PTR_TOP->PTR_MORE + + + + + + +PTR_BOT + +⊥:ptr +[*⊥:struct?] + + + +PTR_BOT->BOT + + + + + +PTR_PERSON_NIL + +*Person? + + + +PTR_NULL->PTR_PERSON_NIL + + + + + +PTR_S1_NIL + +*s1? + + + +PTR_NULL->PTR_S1_NIL + + + + + +PTR_MORE_NIL + +*s2?, … + + + +PTR_NULL->PTR_MORE_NIL + + + + + +PTR_ANY + +*⊥:struct +[ptr any struct] + + + +PTR_ANY->PTR_BOT + + + + + + +PTR_PERSON->PTR_ANY + + + + + + +PTR_PERSON->PTR_PERSON_NIL + + + + + +PTR_S1->PTR_ANY + + + + + + +PTR_S1->PTR_S1_NIL + + + + + + +PTR_MORE->PTR_ANY + + + + + +PTR_MORE->PTR_MORE_NIL + + + + + +PTR_PERSON_NIL->PTR_BOT + + + + + + +PTR_S1_NIL->PTR_BOT + + + + + + +PTR_MORE_NIL->PTR_BOT + + + + + +STRUCT_PERSON + +Person +age: int + + + +STRUCT_TOP->STRUCT_PERSON + + + + + +STRUCT_S1 + +s1 +x: int + + + +STRUCT_TOP->STRUCT_S1 + + + + + +STRUCT_S2 + +s2 +x: int, y: int + + + +STRUCT_TOP->STRUCT_S2 + + + + + +STRUCT_MORE + +… + + + +STRUCT_TOP->STRUCT_MORE + + + + + + +STRUCT_BOT + +⊥:struct +[struct bottom] + + + +STRUCT_BOT->BOT + + + + + +STRUCT_PERSON->STRUCT_BOT + + + + + + +STRUCT_S1->STRUCT_BOT + + + + + + +STRUCT_S2->STRUCT_BOT + + + + + + +STRUCT_MORE->STRUCT_BOT + + + + + +MEM_ALIAS2 + +MEM#2 + + + +MEM_TOP->MEM_ALIAS2 + + + + + +MEM_ALIAS3 + +MEM#3 + + + +MEM_TOP->MEM_ALIAS3 + + + + + +MEM_MORE + +… + + + +MEM_TOP->MEM_MORE + + + + + + +MEM_BOT + +⊥:mem + + + +MEM_BOT->BOT + + + + + +MEM_ALIAS2->MEM_BOT + + + + + + +MEM_ALIAS3->MEM_BOT + + + + + + +MEM_MORE->MEM_BOT + + + + + +TUPLE1 + + + +tuple + + +type + + + +TUPLE_TOP->TUPLE1 + + + + + +TUPLE2 + + + +tuple + + +type + + +type + + + +TUPLE_TOP->TUPLE2 + + + + + +TUPLEN + + + +tuple + + +type + + +… + + +type + + + +TUPLE_TOP->TUPLEN + + + + + +TUPLE_BOT + +⊥:tuple + + + +TUPLE_BOT->BOT + + + + + +TUPLE1->TUPLE_BOT + + + + + + +TUPLE2->TUPLE_BOT + + + + + + +TUPLEN->TUPLE_BOT + + + + + diff --git a/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java deleted file mode 100644 index 35b34576..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ /dev/null @@ -1,272 +0,0 @@ - package com.seaofnodes.simple; - -import com.seaofnodes.simple.IterPeeps.WorkList; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.*; - -public abstract class GlobalCodeMotion { - - // Arrange that the existing isCFG() Nodes form a valid CFG. The - // Node.use(0) is always a block tail (either IfNode or head of the - // following block). There are no unreachable infinite loops. - public static void buildCFG( StopNode stop ) { - fixLoops(stop); - schedEarly(); - Parser.SCHEDULED = true; - schedLate(stop); - } - - // ------------------------------------------------------------------------ - // Backwards walk on the CFG only, looking for unreachable code - which has - // to be an infinite loop. Insert a bogus never-taken exit to Stop, so the - // loop becomes reachable. Also, set loop nesting depth - private static void fixLoops(StopNode stop) { - // Backwards walk from Stop, looking for unreachable code - BitSet visit = new BitSet(); - HashSet unreach = new HashSet<>(); - unreach.add(Parser.START); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - if( unreach.isEmpty() ) return; - - // Forwards walk from unreachable, looking for loops with no exit test. - visit.clear(); - for( CFGNode cfg : unreach ) - walkInfinite(cfg,visit,stop); - // Set loop depth on remaining graph - unreach.clear(); - visit.clear(); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - assert unreach.isEmpty(); - } - - - // Forwards walk over previously unreachable, looking for loops with no - // exit test. - static private void walkInfinite( CFGNode n, BitSet visit, StopNode stop ) { - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - if( n instanceof LoopNode loop ) - loop.forceExit(stop); - for( Node use : n._outputs ) - if( use instanceof CFGNode cfg ) - walkInfinite(cfg,visit,stop); - } - - // ------------------------------------------------------------------------ - private static void schedEarly() { - ArrayList rpo = new ArrayList<>(); - BitSet visit = new BitSet(); - _rpo_cfg(Parser.START, visit, rpo); - // Reverse Post-Order on CFG - for( int j=rpo.size()-1; j>=0; j-- ) { - CFGNode cfg = rpo.get(j); - cfg.loopDepth(); - for( Node n : cfg._inputs ) - _schedEarly(n,visit); - // In dead infinite loops, entire code blocks may be unreachable - // from below. Reach down from the CFG to their Phis so their - // inputs are scheduled too. - if( cfg instanceof RegionNode ) { - int len = cfg.nOuts(); - for( int i=0; i rpo) { - if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) - return; // Been there, done that - visit.set(cfg._nid); - for( Node use : cfg._outputs ) - _rpo_cfg(use,visit,rpo); - rpo.add(cfg); - } - - private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - // Schedule inputs first, except Phis: following their backedges would - // enter a data cycle before its control has been scheduled. - for( Node def : n._inputs ) - if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,visit); - // An existing edge 0 already supplies control (or a Phi/Proj binding). - if( n.in(0)==null ) { - // Schedule at deepest input - CFGNode early = Parser.START; // Maximally early, lowest idepth - for( int i=1; i early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); // First place this can go - } - } - - // ------------------------------------------------------------------------ - private static void schedLate( StopNode stop) { - CFGNode[] late = new CFGNode[Node.UID()]; - Node[] ns = new Node[Node.UID()]; - // Record Load NIDs at all their CFG block choices, then check against - // Store block choices to force a Load above an anti-dependent Store. - int[] anti = new int[Node.UID()]; - // Breadth-first scheduling - breadth(stop,ns,late,anti); - - // Copy the best placement choice into the control slot - for( int i=0; i work = new WorkList<>(); - work.push(stop); - Node n; - outer: - while( (n = work.pop()) != null ) { - assert late[n._nid]==null; // No double visit - // These I know the late schedule of, and need to set early for loops - if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); - else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); - // These nodes have a fixed late placement at their original control. - else if( n instanceof ProjNode || n instanceof NewNode || n==Parser.ZERO ) late[n._nid] = n.cfg0(); - else { - - // All uses done? - for( Node use : n._outputs ) - if( use!=null && late[use._nid]==null ) - continue outer; // Nope, await all uses done - - // Loads need their memory inputs' uses also done - if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( antiUse(ld,memuse) && late[memuse._nid]==null ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late,anti); - } - - // A use just finished; reconsider its inputs and waiting loads, - // even when the shared memory input was already scheduled. - for( Node def : n._inputs ) { - if( def==null ) continue; - if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); - } - if( n instanceof LoopNode loop ) - for( Node phi : loop._outputs ) - if( phi instanceof PhiNode && late[phi._nid]==null ) - work.push(phi); - } - } - - private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { - // Walk uses, gathering the LCA (Least Common Ancestor) of uses - CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); - assert early != null; - CFGNode lca = null; - for( Node use : n._outputs ) - if( use != null ) - lca = use_block(n,use, late).domLCA(lca); - - // Loads may need anti-dependencies, raising their LCA - if( n instanceof LoadNode load ) - lca = find_anti_dep(lca,load,early,late,anti); - - // Walk up from the LCA to the early, looking for best place. This is - // the lowest execution frequency, approximated by least loop depth and - // deepest control flow. - CFGNode best = lca; - lca = lca.idom(); // Already found best for starting LCA - for( ; lca != early.idom(); lca = lca.idom() ) - if( better(lca,best) ) - best = lca; - assert !(best instanceof IfNode); - ns [n._nid] = n; - late[n._nid] = best; - } - - // Block of use. Normally from late[] schedule, except for Phis, which go - // to the matching Region input. - private static CFGNode use_block(Node n, Node use, CFGNode[] late) { - if( !(use instanceof PhiNode phi) ) - return late[use._nid]; - CFGNode found=null; - for( int i=1; i best.idepth() || - best instanceof IfNode; - } - - // Only a store or memory Phi covering this alias can constrain a load. - // MemMerge packages slices without overwriting them. - private static boolean antiUse(LoadNode load, Node use) { - return switch( use ) { - case StoreNode st -> st._alias==load._alias; - case MemPhiNode phi -> phi._alias==load._alias; - case BulkMemPhiNode phi -> !phi.isSplit(load._alias); - default -> false; - }; - } - - private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { - // We could skip final-field loads here. - // Walk LCA->early, flagging Load's block location choices - for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) - anti[cfg._nid] = load._nid; - // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { - if( !antiUse(load,mem) ) continue; - switch( mem ) { - case StoreNode st: - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case PhiNode phi: - // Repeat anti-dep for matching Phi inputs. - // No anti-dep edges but may raise the LCA. - for( int i=1; i { private static final com.seaofnodes.print.IRPrinter PRINT = new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } + @Override public int inputColumns() { return 3; } @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } @Override public Kind kind(Node n) { if( n instanceof StartNode ) return Kind.START; @@ -17,15 +18,13 @@ public final class IRPrinter extends IRAdapter { if( n instanceof PhiNode ) return Kind.PHI; if( n instanceof ReturnNode ) return Kind.RETURN; if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof CProjNode ) return Kind.CPROJ; - if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ; - if( n instanceof CFGNode ) return Kind.CTRL; + if( n instanceof ProjNode ) return n.isCFG() ? Kind.CPROJ : Kind.PROJ; + if( n.isCFG() ) return Kind.CTRL; if( n instanceof MultiNode ) return Kind.MULTI; return Kind.DATA; } @Override public int index(Node n) { if( n instanceof ProjNode p ) return p._idx; - if( n instanceof CProjNode p ) return p._idx; return n._nid; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java index 9d2b913f..7075d526 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -98,7 +98,6 @@ public abstract class IterPeeps { if( obs != null ) obs.after(n, x, true); } - 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 fb4dfdf8..3b5f2721 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java @@ -31,18 +31,12 @@ public class Parser { */ public static StartNode START; - public static ConstantNode ZERO; - public static XCtrlNode XCTRL; - public StopNode STOP; // Field identities are independent of the memory graph and Start's tuple. private final HashMap _aliases = new HashMap<>(); private int _alias = 2; - // Debugger Printing. - public static boolean SCHEDULED; // True if debug printer can use schedule info - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; @@ -96,14 +90,11 @@ public class Parser { Node.reset(); IterPeeps.reset(); OBJS.clear(); - SCHEDULED = false; _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); STOP = new StopNode(source); - ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); - XCTRL= new XCtrlNode().peephole().keep(); } public Parser(String source) { @@ -126,12 +117,10 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); parseBlock(); - if( ctrl()._type==Type.CONTROL ) - STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); _scope.pop(); _xScopes.pop(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); @@ -215,9 +204,9 @@ public class Parser { require("}"); // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); - OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names _aliases.put(typeName,_alias); _alias += ts._fields.length; + OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names return parseStatement(); } @@ -259,8 +248,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); // Clone the body Scope to create the break/exit Scope which accounts for any // side effects in the predicate. The break/exit Scope will be the final @@ -306,7 +295,7 @@ public class Parser { private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth while( cur._scopes.size() > _breakScope._scopes.size() ) @@ -342,8 +331,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current int ndefs = _scope.nIns(); @@ -390,7 +379,7 @@ public class Parser { private Node parseReturn() { var expr = require(parseExpression(), ";"); Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); - ctrl(XCTRL); // Kill control + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control return ret; } @@ -585,7 +574,7 @@ public class Parser { if( _lexer.isNumber() ) return parseIntegerLiteral(); if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); - if( matchx("false") ) return ZERO; + if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); if( matchx("new") ) { String structName = requireId(); @@ -603,11 +592,12 @@ public class Parser { /** * Return a NewNode but also generate instructions to initialize it. */ - private Node newStruct(TypeStruct obj) { + private Node newStruct( TypeStruct obj ) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); + Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); int alias = _aliases.get(obj._name); for( Field field : obj._fields ) { - store(field._fname,alias,n,ZERO); + store(field._fname,alias,n,initValue); alias++; } return n.unkeep(); @@ -619,7 +609,7 @@ public class Parser { private void store(String name, int alias, Node ptr, Node val) { Node prior = mem().keep(); - Node st = new StoreNode(name,alias,ctrl(),prior,ptr,val).peephole(); + Node st = new StoreNode(name,alias,prior,ptr,val).peephole(); mem(new MemMergeNode(prior,alias,st).peephole()); prior.unkeep(); } @@ -759,16 +749,7 @@ public class Parser { * Return the next non-white-space character */ private void skipWhiteSpace() { - while( true ) { - if( isWhiteSpace() ) _position++; - // Skip // to end of line - else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '/') { - _position += 2; - while( !isEOF() && _input[_position] != '\n' ) _position++; - } else break; - } + while (isWhiteSpace()) _position++; } @@ -850,9 +831,8 @@ public class Parser { return new String(_input, start, --_position - start); } - // private boolean isPunctuation(char ch) { - return "=;[]<>()+-/*".indexOf(ch) != -1; + return "=;[]<>(){}+-/*!".indexOf(ch) != -1; } private String parsePunctuation() { diff --git a/chapter11/src/main/java/com/seaofnodes/simple/Utils.java b/chapter11/src/main/java/com/seaofnodes/simple/Utils.java index f3ac25b1..4f2af769 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/Utils.java @@ -55,4 +55,11 @@ public class Utils { // Fold a 64bit hash into 32 bits public static int fold( long x ) { return (int)((x>>32) ^ x); } + + public static boolean eq(String s0, String s1) { + if( s0==s1 ) return true; + assert s0==s0.intern(); + assert s1==s1.intern(); + return false; + } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index ea67d17d..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; - -import java.util.BitSet; -import java.util.HashSet; -import java.util.HashMap; - -/** Control Flow Graph Nodes - *

- * CFG nodes have a immediate dominator depth (idepth) and a loop nesting - * depth(loop_depth). - *

- * idepth is computed lazily upon first request, and is valid even in the - * Parser, and is used by peepholes during parsing and afterward. - *

- * loop_depth is computed after optimization as part of scheduling. - * - */ -public abstract class CFGNode extends Node { - - public CFGNode(Node... nodes) { super(nodes); } - - @Override public boolean isCFG() { return true; } - - public CFGNode cfg(int idx) { return (CFGNode)in(idx); } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return false; } - - // Should be exactly 1 tail from a block head - public CFGNode blockTail() { - assert blockHead(); - for( Node n : _outputs ) - if( n instanceof CFGNode cfg ) - return cfg; - return null; - } - - // ------------------------------------------------------------------------ - /** - * Immediate dominator tree depth, used to approximate a real IDOM during - * parsing where we do not have the whole program, and also peepholes - * change the CFG incrementally. - *

- * See {@link ...} - */ - public char _idepth; - public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - return _idepth = (char)depth; - } - - - // Return the immediate dominator of this Node and compute dom tree depth. - public CFGNode idom() { return cfg(0); } - - // Return the LCA of two idoms - public CFGNode domLCA(CFGNode rhs) { - if( rhs==null ) return this; - CFGNode lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = lhs.idom(); - if( comp <= 0 ) rhs = rhs.idom(); - } - return lhs; - } - - // Loop nesting depth - public int _loopDepth; - public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(0).loopDepth()) : _loopDepth; } - - // ------------------------------------------------------------------------ - // Support routines for Global Code Motion - - // Tik-tok recursion pattern. This method is final, and every caller does - // this work. - public final void walkUnreach( BitSet visit, HashSet unreach ) { - if( visit.get(_nid) ) return; - visit.set(_nid); - _walkUnreach(visit,unreach); - unreach.remove(this); // Since we reached here... Node was not unreachable - } - - // Tik-tok recursion pattern; not-final; callers override this. - void _walkUnreach( BitSet visit, HashSet unreach ) { - cfg(0).walkUnreach(visit,unreach); - } -} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index ff5d1a76..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; - -public class CProjNode extends CFGNode { - - // Which slice of the incoming multipart value - public final int _idx; - - // Debugging label - public final String _label; - - public CProjNode(Node ctrl, int idx, String label) { - super(ctrl); - _idx = idx; - _label = label; - } - - @Override public String label() { return _label; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p(_label); } - - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean blockHead() { return true; } - - public CFGNode ctrl() { return cfg(0); } - - @Override - public Type compute() { - Type t = ctrl()._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if ( ctrl() instanceof IfNode iff ) { - if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) - return iff.in(0); // We become our input control - - // Flip a negating if-test, to remove the not - if( iff.pred().addDep(this) instanceof NotNode not ) - return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - } - return null; - } - - @Override - public Node getBlockStart() { - return ctrl() instanceof IfNode ? this : ctrl().getBlockStart(); - } - @Override - boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } - - @Override - int hash() { return _idx; } - -} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 9ecd1152..426736ed 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -38,7 +38,6 @@ public class ConstantNode extends Node { } @Override public boolean isMultiTail() { return true; } - // The special forever-zero is pinned, all others can sink @Override public Type compute() { return _con; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java index 70340f7f..45f695a5 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -5,10 +5,8 @@ import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeTuple; -import java.util.BitSet; -import java.util.HashSet; -public class IfNode extends CFGNode implements MultiNode { +public class IfNode extends MultiNode { public IfNode(Node ctrl, Node pred) { super(ctrl, pred); @@ -20,6 +18,7 @@ public class IfNode extends CFGNode implements MultiNode { @Override protected String format() { return "if( %1 )"; } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } public Node ctrl() { return in(0); } @@ -49,20 +48,11 @@ public class IfNode extends CFGNode implements MultiNode { // Hunt up the immediate dominator tree. If we find an identical if // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { + for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); return this; } return null; } - - @Override void _walkUnreach( BitSet visit, HashSet unreach ) { - for( Node proj : _outputs ) - if( ((CProjNode)proj)._loopDepth == 0 ) - unreach.add((CProjNode)proj); - super._walkUnreach(visit,unreach); - } - - @Override public Node getBlockStart() { return ctrl().getBlockStart(); } } 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 386009b4..9bf36459 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -23,7 +23,7 @@ public class LoadNode extends MemOpNode { * @param memPtr The ptr to the struct from where we load a field */ public LoadNode(String name, int alias, Type glb, Node memSlice, Node memPtr) { - super(name, alias, null, memSlice, memPtr); + super(name, alias, memSlice, memPtr, null); _declaredType = glb; } @@ -67,7 +67,7 @@ public class LoadNode extends MemOpNode { // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && ptr() == st.ptr() && _alias==st._alias ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect + assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect return st.val(); } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 45549f1d..a0ba973e 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -1,17 +1,15 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; + import java.util.BitSet; -import java.util.HashSet; public class LoopNode extends RegionNode { + public LoopNode( Node entry ) { super(null,entry,null); } - public CFGNode entry() { return cfg(1); } - public CFGNode back () { return cfg(2); } + Node entry() { return in(1); } + Node back () { return in(2); } @Override public String label() { return "Loop"; } @@ -27,51 +25,7 @@ public class LoopNode extends RegionNode { return inProgress() ? null : super.idealize(); } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public CFGNode idom() { return entry(); } - - @Override public int loopDepth() { - if( _loopDepth!=0 ) return _loopDepth; // Was already set - _loopDepth = entry()._loopDepth+1; // Entry depth plus one - // One-time tag loop exits - for( CFGNode idom = back(); idom!=this; idom = idom.idom() ) { - // Walk idom in loop, setting depth - idom._loopDepth = _loopDepth; - // Loop exit hits the CProj before the If, instead of jumping from - // Region directly to If. - if( idom instanceof CProjNode proj ) { - assert proj.in(0) instanceof IfNode; // Loop exit test - // Find the loop exit CProj, and set loop_depth - for( Node use : proj.in(0)._outputs ) - if( use instanceof CProjNode proj2 && proj2 != idom ) - proj2._loopDepth = _loopDepth-1; - } - } - return _loopDepth; - } - - // If this is an unreachable loop, it may not have an exit. If it does not - // (i.e., infinite loop), force an exit to make it reachable. - public void forceExit( StopNode stop ) { - // Walk the backedge, then immediate dominator tree util we hit this - // Loop again. If we ever hit a CProj from an If (as opposed to - // directly on the If) we found our exit. - CFGNode x = back(); - while( x != this ) { - if( x instanceof CProjNode exit ) - return; // Found an exit, not an infinite loop - x = x.idom(); - } - // Found a no-exit loop. Insert an exit - NeverNode iff = new NeverNode(back()); - for( Node use : _outputs ) - if( use instanceof PhiNode phi ) - iff.addDef(use); - CProjNode t = new CProjNode(iff,0,"True" ); - CProjNode f = new CProjNode(iff,1,"False"); - setDef(2,f); - stop.addDef(new ReturnNode(t,Parser.ZERO,null)); - } + // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) + @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } + @Override Node idom() { return entry(); } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 5a57a8c8..44304bcb 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -12,8 +12,8 @@ public abstract class MemOpNode extends Node { public final String _name; public final int _alias; - public MemOpNode(String name, int alias, Node... nodes) { - super(nodes); + public MemOpNode(String name, int alias, Node memSlice, Node memPtr, Node value) { + super(null, memSlice, memPtr, value); _name = name; _alias = alias; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 379e140d..2743bd19 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,10 +1,12 @@ package com.seaofnodes.simple.node; -public interface MultiNode extends OutNode { +public abstract class MultiNode extends Node { + + public MultiNode(Node... inputs) { super(inputs); } // Find a projection by index - default ProjNode proj( int idx ) { - for( Node out : outs() ) + ProjNode proj( int idx ) { + for( Node out : _outputs ) if( out instanceof ProjNode prj && prj._idx==idx ) return prj; return null; diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java deleted file mode 100644 index 6eb7fe63..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.Type; - -// "Never true" for infinite loop exits -public class NeverNode extends IfNode { - public NeverNode(Node ctrl) { super(ctrl,Parser.ZERO); } - - @Override public String label() { return "Never"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p("Never"); } - - @Override public Type compute() { return TypeInteger.BOT; } - - @Override public Node idealize() { return null; } -} 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 74ba5184..857920bc 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -22,7 +22,6 @@ public class NewNode extends Node { return p.p("new ").p(_ptr._obj.str()); } - @Override public Type compute() { return _ptr; } 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 568c23cc..1d8e23f8 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,8 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.BaseNode; -import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.Parser; + +import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; @@ -16,7 +17,7 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node extends BaseNode implements OutNode, Cloneable { +public abstract class Node extends BaseNode implements Cloneable { /** * Inputs to the node. These are use-def references to Nodes. @@ -82,7 +83,6 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable */ public Node in(int i) { return _inputs.get(i); } public Node out(int i) { return _outputs.get(i); } - @Override public ArrayList outs() { return _outputs; } public int nIns() { return _inputs.size(); } @@ -90,7 +90,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable public boolean isUnused() { return nOuts() == 0; } - public CFGNode cfg0() { return (CFGNode)in(0); } + public boolean isMem() { return false; } /** * Change a def into a Node. Keeps the edges correct, by removing @@ -117,11 +117,11 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // the new_def might get killed if the old node kills it recursively. if( new_def != null ) new_def.addUse(this); - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); if( old_def != null && // If the old def exists, remove a def->use edge old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def + // Set the new_def over the old (killed) edge + _inputs.set(idx,new_def); moveDepsToWorklist(); // Return self for easy flow-coding return new_def; @@ -149,7 +149,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable * @param new_def the new definition, appended to the end of existing definitions * @return new_def for flow coding */ - public Node addDef(Node new_def) { + Node addDef(Node new_def) { unlock(); // Add use->def edge _inputs.add(new_def); @@ -268,8 +268,8 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable Type old = setType(compute()); // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && !(this instanceof XCtrlNode) && _type.isHighOrConst() ) - return (_type==Type.XCONTROL ? new XCtrlNode() : new ConstantNode(_type)).peepholeOpt(); + if( !(this instanceof ConstantNode) && _type.isHighOrConst() ) + return new ConstantNode(_type).peepholeOpt(); // Global Value Numbering if( _hash==0 ) { @@ -483,17 +483,6 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // Subclasses add extra hash info (such as ConstantNodes constant) int hash() { return 0; } - // ------------------------------------------------------------------------ - // - - /** Is this Node control-flow-graph related */ - - /** Is this Node Memory related */ - public boolean isMem() { return false; } - - /** Return block start from a isCFG() */ - public Node getBlockStart() { return null; } - // ------------------------------------------------------------------------ // Peephole utilities @@ -522,6 +511,38 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable return true; } + /** + * Immediate dominator tree depth, used to approximate a real IDOM depth + * during parsing where we do not have the whole program, and also + * peepholes change the CFG incrementally. + *

+ * See {@link ...} + */ + public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit + + // Find the lowest common ancestor in the current dominator tree. + Node domLCA(Node rhs) { + if( rhs==null ) return this; + Node lhs = this; + while( lhs != rhs ) { + if( lhs==null || rhs==null ) return null; + int comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } + + int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } + // Zero depth means uncached. Check before narrowing so overflow cannot wrap. + final int cacheIDepth(int depth) { + assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + return _idepth = (char)depth; + } + + // Return the immediate dominator of this Node. + Node idom() { return in(0); } + // Make a shallow copy (same class) of this Node, with given inputs and // empty outputs and a new Node ID. The original inputs are ignored. // Does not need to be implemented in isCFG() nodes. diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java deleted file mode 100644 index 7e531145..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.seaofnodes.simple.node; - -import java.util.ArrayList; - -public interface OutNode { - ArrayList outs(); -} 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 8f3cda9e..6995cc2f 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -32,9 +32,10 @@ public class PhiNode extends Node { return p.unchar(',').close(); } - public CFGNode region() { return (CFGNode)in(0); } + Node region() { return in(0); } @Override public boolean isMultiTail() { return true; } - @Override public boolean isMem() { return _declaredType instanceof TypeMem; } + @Override + public boolean isMem() { return _declaredType instanceof TypeMem; } @Override public Type compute() { @@ -84,9 +85,9 @@ public class PhiNode extends Node { Node val = in(3-nullx); if( region().idom() instanceof IfNode iff && iff.pred().addDep(this)==val ) { // Must walk the idom on the null side to make sure we hit False. - CFGNode idom = (CFGNode)region().in(nullx); + Node idom = region().in(nullx); while( idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof CProjNode proj && proj._idx==1 ) + if( idom instanceof ProjNode proj && proj._idx==1 ) return val; } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java index e82c69b3..a2632110 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -15,7 +15,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - public ProjNode(Node ctrl, int idx, String label) { + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; _label = label; @@ -27,16 +27,30 @@ public class ProjNode extends Node { @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p(_label); } + @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; } @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean isMem() { return _type instanceof TypeMem; } + public MultiNode ctrl() { return (MultiNode)in(0); } + @Override public Type compute() { Type t = in(0)._type; return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; } - @Override public Node idealize() { return null; } + @Override + public Node idealize() { + if ( ctrl() instanceof IfNode iff ) { + if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) + return iff.in(0); // We become our input control + + // Flip a negating if-test, to remove the not + if( iff.pred().addDep(this) instanceof NotNode not ) + return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); + } + return null; + } @Override boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java index f0808410..8b52494a 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -3,15 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; + import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; -import java.util.HashSet; -public class RegionNode extends CFGNode { +public class RegionNode extends Node { public RegionNode(Node... nodes) { super(nodes); } @@ -22,8 +20,8 @@ public class RegionNode extends CFGNode { return p.p(label()).p(_nid); } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } @Override public Type compute() { @@ -59,7 +57,7 @@ public class RegionNode extends CFGNode { IterPeeps.addAll(phi._outputs); } } - return isDead() ? Parser.XCTRL : delDef(path); + return isDead() ? new ConstantNode(Type.XCONTROL) : delDef(path); } // If down to a single input, become that input if( nIns()==2 && !hasPhi() ) @@ -91,30 +89,23 @@ public class RegionNode extends CFGNode { } // Immediate dominator of Region is a little more complicated. - @Override public int idepth() { + @Override int idepth() { if( _idepth!=0 ) return _idepth; int depth=0; for( Node n : _inputs ) if( n!=null ) - depth = Math.max(depth,((CFGNode)n).idepth()+1); + depth = Math.max(depth,n.idepth()+1); return cacheIDepth(depth); } - @Override public CFGNode idom() { - CFGNode lca = null; + @Override Node idom() { + Node lca = null; // Recompute from predecessors: CFG edits can change the dominator. for( int i=1; i unreach ) { - for( int i=1; i1 && in(nIns()-1) == null; @@ -124,6 +115,4 @@ public class RegionNode extends CFGNode { @Override boolean eq( Node n ) { return !inProgress(); } - - @Override public Node getBlockStart() { return this; } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 161cc7d6..ef4ddb3a 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -6,11 +6,10 @@ import com.seaofnodes.simple.type.*; /** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ -public class ReturnNode extends CFGNode { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // A synthetic never-taken loop exit has no source scope. - super(ctrl, scope == null ? null : scope.lookup("$mem"), data); + super(ctrl, scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } @@ -27,6 +26,8 @@ public class ReturnNode extends CFGNode { return p.p(";"); } + @Override public boolean isCFG() { return true; } + @Override public Type compute() { // Return exposes the complete memory state. @@ -39,6 +40,4 @@ public class ReturnNode extends CFGNode { return ctrl(); return null; } - - @Override public Node getBlockStart() { return ctrl().getBlockStart(); } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java index c1b57915..3404f111 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,13 +3,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.*; -import java.util.BitSet; -import java.util.HashSet; -/** Start supplies control, whole memory, and the argument, in that order. */ -public class StartNode extends CFGNode implements MultiNode { - - private final TypeTuple _args; +/** The entry tuple: control, initial whole-memory state, and argument. */ +public class StartNode extends MultiNode { + final TypeTuple _args; public StartNode(Type[] args) { super(); @@ -23,8 +20,8 @@ public class StartNode extends CFGNode implements MultiNode { return p.p(label()); } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } @Override public TypeTuple compute() { return _args; } @@ -33,12 +30,6 @@ public class StartNode extends CFGNode implements MultiNode { public Node idealize() { return null; } // No immediate dominator, and idepth==0 - @Override public int idepth() { return 0; } - @Override public CFGNode idom() { return null; } - - @Override void _walkUnreach( BitSet visit, HashSet unreach ) { } - - @Override public int loopDepth() { return (_loopDepth=1); } - - @Override public Node getBlockStart() { return this; } + @Override int idepth() { return 0; } + @Override Node idom() { return null; } } 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 46cf51da..4b4e9615 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; -public class StopNode extends CFGNode { +public class StopNode extends Node { public final String _src; @@ -31,7 +31,7 @@ public class StopNode extends CFGNode { return p.p("]"); } - @Override public boolean blockHead() { return true; } + @Override public boolean isCFG() { return true; } // If a single Return, return it. // Otherwise, null because ambiguous. @@ -54,17 +54,7 @@ public class StopNode extends CFGNode { return null; } - @Override public int idepth() { - if( _idepth!=0 ) return _idepth; - int d=0; - for( Node n : _inputs ) - if( n!=null ) - d = Math.max(d,((CFGNode)n).idepth()+1); - return cacheIDepth(d); - } - @Override public CFGNode idom() { return null; } - - @Override public int loopDepth() { return (_loopDepth=1); } + @Override public Node idom() { return null; } public Node addReturn(Node node) { return addDef(node); @@ -82,6 +72,4 @@ public class StopNode extends CFGNode { if( obs != null ) obs.phase("TypeCheck"); return this; } - - @Override public Node getBlockStart() { return this; } } 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 f61afe78..026d5c50 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -19,8 +19,8 @@ public class StoreNode extends MemOpNode { * @param memPtr The ptr to the struct where we will store a value * @param value Value to be stored */ - public StoreNode(String name, int alias, Node ctrl, Node memSlice, Node memPtr, Node value) { - super(name, alias, ctrl, memSlice, memPtr, value); + public StoreNode(String name, int alias, Node memSlice, Node memPtr, Node value) { + super(name, alias, memSlice, memPtr, value); } @Override @@ -53,7 +53,7 @@ public class StoreNode extends MemOpNode { // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. st.checkNoUseBeyond(this) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect + assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect setDef(1,st.mem()); return this; } 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 7b4cff0b..6fa87a07 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java deleted file mode 100644 index 056dfba1..00000000 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - -public class XCtrlNode extends CFGNode { - public XCtrlNode( ) { super(Parser.START); } - @Override public String label() { return "Xctrl"; } - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p("Xctrl"); } - @Override public Type compute() { return Type.XCONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 89ef99bd..92d970d8 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,13 +6,13 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 11's view of the IR; browser, transport and layout are shared. */ +/** Chapter 10's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter { @Override protected String[] edgeNames(Node n) { return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } + @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : -1; } @Override protected Kind kind(Node n) { if( n instanceof StopNode ) return Kind.STOP; @@ -25,9 +25,9 @@ public class SimpleGraphAdapter extends GraphAdapter { } @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + 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 || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : 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; diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java index b50a4fa6..cdc58215 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java @@ -20,7 +20,6 @@ public class Field extends Type { _fname = fname; _type = type; } - // Make with existing alias public static Field make( String fname, Type type ) { return new Field(fname,type).intern(); } @@ -47,7 +46,7 @@ public class Field extends Type { boolean eq(Type t) { Field f = (Field)t; - return _fname.equals(f._fname) && _type==f._type; + return Utils.eq(_fname,f._fname) && _type==f._type; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index ec7390ca..0aa019a7 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -58,7 +58,8 @@ public class TypeTuple extends Type { for( Type t : _types ) t.typeName(sb).append(","); sb.setLength(sb.length()-1); - return sb.append("]"); + sb.append("]"); + return sb; } public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java index c10dc510..89882b2f 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -15,7 +15,7 @@ public class Chapter01Test { StartNode start = Parser.START; ReturnNode ret = (ReturnNode)stop.in(0); - assertTrue(ret.ctrl() instanceof CProjNode); + assertTrue(ret.ctrl() instanceof ProjNode); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { assertEquals(start,con.in(0)); diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 14d31245..edc62f50 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -83,7 +83,7 @@ public class Chapter04Test { Parser parser = new Parser("return arg; "); StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); - assertTrue(ret.in(0) instanceof CProjNode); + assertTrue(ret.in(0) instanceof ProjNode); assertTrue(ret.in(1) instanceof ProjNode); assertEquals(1, ((ProjNode)ret.in(1))._idx); assertTrue(ret.in(2) instanceof ProjNode); diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 7beeb4d9..cf7bd6cf 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region21,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); } @Test @@ -37,7 +37,7 @@ if (arg == 1) { } return c;""", TypeInteger.BOT); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region17,4,3);", ret.toString()); } @Test @@ -65,7 +65,7 @@ else b=a+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region35,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); } @Test @@ -100,7 +100,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,4,5));", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); } @Test @@ -116,7 +116,7 @@ if( arg==0 ) return arg+a+b; """, TypeInteger.BOT); StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region16,1,0))+Phi(Region31,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region17,3,2));", ret.toString()); } @Test diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java index df0f2848..920e26e3 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -17,7 +17,7 @@ return 1; """); StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -30,7 +30,7 @@ if (arg) return (arg < a) < 3; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((arg 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 4c2b42f1..5f92c9c1 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,567 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } - } - } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } - } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } - } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); - } - - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region82,Phi_primeCount,Phi(Region77,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; } -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; } -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,Phi(Region39,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( - """ -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); - } - - @Test - public void testSplit() { - Parser parser = new Parser( - """ -struct S { int f; } -S s = new S; -if( arg==0 ) s.f = 1; -else if (arg == 1) s.f = 1; -return s.f; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region41,1,Phi(Region36,1,0));", stop.toString()); - assertEquals(1L, Evaluator.evaluate(stop, 0)); - assertEquals(1L, Evaluator.evaluate(stop, 1)); - assertEquals(0L, Evaluator.evaluate(stop, 2)); - } - - @Test - public void testScheduleUse() { - Parser parser = new Parser( -""" -int v0=0; -while(0>=0) { - int v1=0; - v1=v0; - if(v1*0) - v0=-v1; -} -return 0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testLoopCarriedDep() { - Parser parser = new Parser( -""" -int v0=0; -{ - int v1=0; - while(v1) { - v1=1+(v0!=0); - v0=v1/0; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); } } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index 52c2a014..019140ad 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -29,8 +29,8 @@ public class FuzzerWrap { fuzzer.fuzzPeepsRegression(seed); } - @Ignore @Test + @Ignore public void fuzzPeepsLarge() { var fuzzer = new Fuzzer(); for (int i=0; i<1000000; i++) diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 355d906b..ac7c5e09 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -140,8 +140,7 @@ public class Evaluator { case StoreNode store -> store(store); case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); - case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; - case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; + case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; default -> throw new AssertionError("Unexpected node " + node); }; } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index d16f4300..48ac0562 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -164,7 +164,7 @@ public class Scheduler { * @return true if the node is not XCtrl. */ private static boolean isNotXCtrl(Node node) { - return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL) && !(node instanceof XCtrlNode); + return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); } /** @@ -215,7 +215,7 @@ public class Scheduler { private void refinePlacement(NodeData data, BasicBlock block) { assert !isPinnedNode(data); data.block = data.block == null ? block : dom(data.block, block); - assert isValid(data); + assert isValid(data) : data.node.uniqueName()+" at "+data.block.entry.uniqueName(); } /** @@ -328,8 +328,6 @@ public class Scheduler { for(int i=1; id.block==null).orElse(false)) return false; } - } else if (node instanceof XCtrlNode) { - return false; } else { assert d(node.in(0)).block != null; } @@ -405,7 +403,7 @@ public class Scheduler { } } if (!(node instanceof ReturnNode)) - for (Node n:node._outputs) if (n!=null && n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); + for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); var b = block; for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); } @@ -451,7 +449,7 @@ public class Scheduler { node = data.node; assert node.isCFG(); if (!(node instanceof ReturnNode)) { - for (var out : node._outputs) if (out!=null && out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); + for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); } for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); } @@ -505,7 +503,7 @@ public class Scheduler { */ private static Node findSingleCFGOut(Node node) { if (node instanceof StartNode) { - for(var n:node._outputs) if (n instanceof CProjNode p && p._idx==0) return p; + for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; return null; } assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; @@ -572,7 +570,7 @@ public class Scheduler { switch(block.exit) { case null: break; case IfNode i: - for(var out:i._outputs) if (out instanceof CProjNode p) block.next[p._idx] = blocks.get(p); + for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); break; case RegionNode r: block.next[0] = blocks.get(r); diff --git a/chapter12/Makefile b/chapter12/Makefile index 3f1e53ff..5ed21084 100644 --- a/chapter12/Makefile +++ b/chapter12/Makefile @@ -1,106 +1,7 @@ -SHELL := /bin/bash -# Shared packaging target; included after chapter source variables below. -.DELETE_ON_ERROR: +# Standalone compiler snapshot; shared rules also work in linearized checkouts. +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) +test_cp += com.seaofnodes.simple.FuzzerWrap -# for printing variable values -# usage: make print-VARIABLE -# > VARIABLE = value_of_variable -print-% : ; @echo $* = $($*) - -# literal space -space := $() $() - -# Decide OS-specific questions -# jar-file seperator -ifeq ($(OS),Windows_NT) - SEP = ; -else - SEP = : -endif -# Find a reasonable ctags. -CTAGS = $(shell which ctags) -# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew -ifeq ($(UNAME),Darwin) - CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags) -endif - -# Fun Args to javac. Mostly limit to java8 source definitions, and fairly -# aggressive lint warnings. -JAVAC_ARGS = -g - -# Source code -SIMPLE := com/seaofnodes/simple -SRC := src/main/java -TST := src/test/java -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)) -include $(firstword $(wildcard ../print/print.mk print/print.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) -# All the libraries -libs = $(wildcard lib/*jar) -jars = $(subst $(space),$(SEP),$(libs)) - - -default_targets := $(main_classes) $(test_classes) -# Optionally add ctags to the default target if a reasonable one was found. -ifneq ($(CTAGS),) -default_targets += tags -endif - -default: $(default_targets) - -# Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) $(print_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) - -$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) - @echo "compiling " $@ " because " $? - @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test - @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) - -# Base launch line for JVM tests -JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" - -tests: $(default_targets) - @echo "testing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore $(test_cp) com.seaofnodes.simple.FuzzerWrap - -fuzzer: $(default_targets) - @echo "fuzzing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap - - -.PHONY: clean -clean: - rm -rf build - rm -f TAGS - (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0) - -# Download libs from maven -lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar - -# Jars -lib/junit-4.12.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar) - -lib/hamcrest-core-1.3.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar) - -# Build emacs tags (part of a tasty emacs ide experience) -tags: $(main_javas) $(test_javas) - @rm -f TAGS - @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) - -include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) - -.PHONY: build -build: $(main_classes) +.PHONY: fuzzer +fuzzer: $(test_classes) + java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter12/README.md b/chapter12/README.md index 36922373..3110d735 100644 --- a/chapter12/README.md +++ b/chapter12/README.md @@ -1,61 +1,148 @@ -# Chapter 12: Floats +# Chapter 12: References [Previous: Chapter 11](../chapter11/README.md) | [Next: Chapter 13](../chapter13/README.md) -# Table of Contents +Chapters 10 and 11 gave structs integer fields and represented their memory +effects in SSA. This chapter lets fields contain references to other structs, +including structs whose definitions have not yet appeared. Linked lists and +mutually referring objects become possible without changing the memory graph. -1. [Float](#floats) +You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter12) +in the linear Git history and [compare it with Chapter 11](https://github.com/SeaOfNodes/Simple/compare/linear-chapter11...linear-chapter12). -In this chapter, we add a floating point type and values. +## Typed fields and forward references -You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter12) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter11...linear-chapter12) it to the previous chapter. +```java +struct Person { + int age; + FamilyTree? tree; +} +struct FamilyTree { + Person? father; + Person? mother; +} +``` + +The parser's `TYPES` table maps names to types. It initially contains `int` +(and any other primitive types, more are showing up in later chapters). When +`type()` recognizes a declaration using an unknown name, it installs a +name-only `TypeStruct`, with `_fields == null` - this a struct where we only +know the struct name, and no other information. A pointer to that struct is +enough to describe a reference field,`?` allows the pointer to be null. -## Floats +`parseStruct()` collects all fields before installing the complete struct in +`TYPES`. It replaces the table entry; it does not mutate the old type or +publish successive prefixes of the field list. Field aliases are allocated +with the completed declaration, as before. -Floating point values start with the `flt` type and compute IEEE754 64-bit arithmetic. -Integer expressions auto-widen to floats when involved in a float expression. +See [the parser](src/main/java/com/seaofnodes/simple/Parser.java), particularly +`type()`, `parseField()`, and `parseStruct()`, and the +[grammar](docs/12-grammar.md). + +## Recursive structs: `L0` and `L1` ```java -flt f = 0.5; -int x = 2; -f = f+x; // Auto-widen x to 2.0, result is 2.5 -f = f+true; // Since bools are just synonyms for integers 0,1 they auto-widen also +struct LLI { LLI? next; int i; } ``` -There is no corresponding way right now to round a `flt` back to an `int`: -```java -int x = 3.14; // error +Two descriptions are useful for remembering what the compiler actually builds: + +```text +L0 = TypeStruct("LLI", fields = null) +L1 = TypeStruct("LLI", fields = { next: pointer-to-L0?, i: int }) +TYPES["LLI"] = L1 ``` -Here is Newton's method for computing a square root: +`L0` is created when parsing the `LLI? next` field. After the closing brace, +`L1` replaces it in the name table. The `next` field inside `L1` still points +to `L0`; it is not patched to point back to `L1`. + +The design is **immutable shallow references, resolved by name when needed**. +The runtime objects may form a cycle; their compiler type descriptions remain +finite and acyclic. This is an intentional decision to **stop recursive type +expansion**. Recursively substituting the completed definition into every +reference would either expand forever or require cyclic type interning, +equality, hashing, and lattice operations (which indeed show up in a lattice +chapter). + +[`TypeStruct`](src/main/java/com/seaofnodes/simple/type/TypeStruct.java) +instead stops at the name-only description. Its `dual()` and `glb()` leave that +description alone. Meeting a complete description with a name-only description +of the same struct yields the name-only description. That loses field detail +without losing the struct's identity. There is no pass trying to expand `L0` +into an ever deeper sequence of `L1`, `L2`, and so on. + +## Resolving a shallow reference + +When parsing `head.next`, `parsePostfix()` takes the pointer's struct name and +looks up the current declaration in `TYPES`. It gets the field layout and alias +from that declaration, even if the pointer's own type still contains `L0`. +Doing the lookup again for the next field access is sufficient for a chain +such as `head.next.next.i`. + +For a local declaration or assignment, `parseExpressionStatement()` also +resolves a name-only pointer before checking assignment compatibility. This +"deepening" changes the temporary type used for the check; it does not rewrite +the expression node's type or replace all old references in the graph. + ```java -flt guess = arg; // Initial guess is just argument -while( true ) { - // Next guess from Newton's method - flt next = (arg/guess + guess)/2; - // Break if hit fixed point - if( next == guess ) break; - guess = next; +struct LLI { LLI? next; int i; } +LLI? head = null; +while (arg) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg - 1; } -return guess; +if (!head) return 0; +LLI? next = head.next; +if (next == null) return 1; +return next.i; ``` -When run with argument 2.0, produces 1.414213562373095. -Floating point operations have their own Nodes: +The null checks refine the pointer on the surviving control path. In +particular, the false path of an `if` must record its refinement even when +there is no explicit `else`. Resolving a name supplies the field definition; +it does not prove that the pointer is non-null. -| Node | Op | -|------------------------|----------------------| -| AddF, SubF, MulF, DivF | Basic binary FP ops | -| MinusF | Unary negate | -| EQF, LTF, LEF | FP compare operators | +## Initialization and incomplete definitions -Within the Type lattice, we add the following domain: +`newStruct()` initializes each field using its type's `makeInit()`: zero for +integers and null for references. Loads and Stores use the same aliases and +memory optimizations as Chapter 11. A Load from an initializing Store can +therefore fold to null. -* ![Float](../docs/type-float.svg) - Float values. The same shape as the Integer values, but floats - with a top (`TOP`, ⊤), bottom (`BOT`, ⊥), and constants. +An unused forward reference need not ever be defined: + +```java +struct Holder { Missing? ref; } +Holder h = new Holder; +return h.ref; // null; no definition of Missing is needed +``` + +There is no way here to construct a non-null `Missing` without its complete +definition: `new Missing` is rejected. Accessing one of its fields also needs +a definition, as well as a non-null pointer. Simply mentioning a nullable +reference type does not impose a requirement to define it before the end of +the program. + +There is one temporary initialization allowance in this chapter: + +```java +struct N { N next; int i; } +N n = new N; +return n.next; // null, despite the non-null field declaration +``` -The other domains retain Chapter 10b's named structs, nullable pointers, and -precise memory aliases. +The initializing Store may write null even into a non-null reference field. +An ordinary `n.next = null` is rejected. Chapter 16 adds constructors and +checks initialization; this chapter keeps the distinction explicit in +[`StoreNode`](src/main/java/com/seaofnodes/simple/node/StoreNode.java). -![Graph1](./docs/lattice.svg) +The [reference tests](src/test/java/com/seaofnodes/simple/Chapter12Test.java) +exercise linked lists, mutual references, null checks, and incomplete types. +Run them with `make tests`; `make view` displays the same unscheduled graph. +Chapter 13 now has the memory and reference semantics it needs to introduce +global code motion, including load/store anti-dependencies. diff --git a/chapter12/chapter12.iml b/chapter12/chapter12.iml new file mode 100644 index 00000000..28f6886a --- /dev/null +++ b/chapter12/chapter12.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chapter12/docs/12-grammar.md b/chapter12/docs/12-grammar.md index 4c071a98..24199ede 100644 --- a/chapter12/docs/12-grammar.md +++ b/chapter12/docs/12-grammar.md @@ -11,21 +11,21 @@ statement : returnStatement | structDeclaration | declStatement - | blockStatment + | blockStatement | expressionStatement | ifStatement | whileStatement | breakStatement - | continueStatment + | continueStatement ; PRIMTYPE : 'int' - | 'flt' ; field : PRIMTYPE IDENTIFIER ';' + | structName ('?')? IDENTIFIER ';' ; fields @@ -102,7 +102,7 @@ newExpression ; fieldExpression - : primaryExpresson '.' IDENTIFIER + : primaryExpression '.' IDENTIFIER ; primaryExpression @@ -122,7 +122,7 @@ INTEGER_LITERAL ; IDENTIFIER - : NON_DIGIT (NON_DIGIT | DIGIT)* + : NON_DIGIT (NON_DIGIT | DEC_DIGIT)* ; NON_DIGIT: [a-zA-Z_]; diff --git a/chapter12/docs/lattice.gv b/chapter12/docs/lattice.gv deleted file mode 100644 index e8631cd5..00000000 --- a/chapter12/docs/lattice.gv +++ /dev/null @@ -1,154 +0,0 @@ -digraph Lattice { - rankdir=TB; - newrank=true; - // Preserve the same-rank ordering edges through cluster layout. - remincross=false; - nodesep=0.22; - ranksep=0.65; - node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; - edge [color="#555555", arrowsize=0.6]; - - // These arrows are lattice-order edges, not use/definition edges. - TOP [label="⊤", fillcolor=black, fontcolor=white]; - BOT [label="⊥", fillcolor=black, fontcolor=white]; - - subgraph cluster_control { - label="Control"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffff00"]; - XCTRL [label="⊤:ctrl"]; - CTRL [label="⊥:ctrl"]; - XCTRL -> CTRL; - } - - subgraph cluster_integer { - label="Integer"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#add8e6"]; - INT_TOP [label="⊤:int"]; - INT_BOT [label="⊥:int"]; - INT_NEG [label="… −2, −1"]; - INT_ZERO [label="0"]; - INT_POS [label="1, 2, …"]; - {rank=same; INT_NEG; INT_ZERO; INT_POS;} - INT_NEG -> INT_ZERO -> INT_POS [style=invis]; - INT_TOP -> {INT_NEG INT_ZERO INT_POS}; - {INT_NEG INT_ZERO INT_POS} -> INT_BOT; - } - - subgraph cluster_float { - label="Float"; fontname="Arial"; color="#cccccc"; - FLT_TOP [label=<⊤:flt>, fillcolor=aqua, shape=oval]; - FLT_BOT [label=<⊥:flt>, fillcolor=aqua, shape=oval]; - - FLT_MINDOTS [label="… negative constants", fillcolor=aqua, shape=oval]; - FLT_ZERO [label="0.0" , fillcolor=aqua]; - FLT_MAXDOTS [label="positive constants …", fillcolor=aqua, shape=oval]; - - FLT_TOP -> FLT_MINDOTS; - FLT_TOP -> FLT_ZERO; - FLT_TOP -> FLT_MAXDOTS; - FLT_MINDOTS -> FLT_BOT; - FLT_ZERO -> FLT_BOT; - FLT_MAXDOTS -> FLT_BOT; - - FLT_MINDOTS -> FLT_ZERO -> FLT_MAXDOTS [style=invis]; - - {rank=same; FLT_MINDOTS; FLT_ZERO; FLT_MAXDOTS;} - } - - subgraph cluster_pointer { - label="Pointer = nullability × struct type"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#008000", fontcolor=white]; - PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; - PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; - PTR_NULL [label="null"]; - PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; - PTR_PERSON [label="*Person"]; - PTR_S1 [label="*s1"]; - PTR_MORE [label="*s2, …"]; - PTR_PERSON_NIL [label="*Person?"]; - PTR_S1_NIL [label="*s1?"]; - PTR_MORE_NIL [label="*s2?, …"]; - {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} - {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} - PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; - PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; - PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; - {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; - PTR_PERSON -> PTR_PERSON_NIL; - PTR_S1 -> PTR_S1_NIL; - PTR_MORE -> PTR_MORE_NIL; - PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; - {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; - } - - subgraph cluster_struct { - label="Struct\nnamed collection of named scalar types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#90ee90"]; - STRUCT_TOP [label="⊤:struct\n[struct top]"]; - STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; - STRUCT_PERSON [label="Person\nage: int", shape=box]; - STRUCT_S1 [label="s1\nx: int", shape=box]; - STRUCT_S2 [label="s2\nx: int, y: flt", shape=box]; - STRUCT_MORE [label="…", shape=box]; - {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} - STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; - STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; - {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; - } - - subgraph cluster_memory { - label="Memory\nprecise alias slices"; fontname="Arial"; color="#cccccc"; - node [fillcolor="#00008b", fontcolor=white]; - MEM_TOP [label="⊤:mem"]; - MEM_BOT [label="⊥:mem"]; - MEM_ALIAS2 [label="MEM#2"]; - MEM_ALIAS3 [label="MEM#3"]; - MEM_MORE [label="…"]; - {rank=same; MEM_ALIAS2; MEM_ALIAS3; MEM_MORE;} - MEM_ALIAS2 -> MEM_ALIAS3 -> MEM_MORE [style=invis]; - MEM_TOP -> {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE}; - {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE} -> MEM_BOT; - } - - subgraph cluster_tuple { - label="Tuple\ncollection of types"; - fontname="Arial"; color="#cccccc"; - node [fillcolor="#ffc0cb"]; - TUPLE_TOP [label="⊤:tuple"]; - TUPLE_BOT [label="⊥:tuple"]; - // Each black element stands for a recursively nested type lattice. - TUPLE1 [shape=plain, label=< - - - -
tuple
type
- >]; - TUPLE2 [shape=plain, label=< - - - - -
tuple
type
type
- >]; - TUPLEN [shape=plain, label=< - - - - - -
tuple
type
…
type
- >]; - {rank=same; TUPLE1; TUPLE2; TUPLEN;} - TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; - TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; - {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; - } - - TOP -> {XCTRL INT_TOP FLT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; - {CTRL INT_BOT FLT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; - {rank=same; XCTRL; INT_TOP; FLT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} - {rank=same; CTRL; INT_BOT; FLT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} - XCTRL -> INT_TOP -> FLT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; -} diff --git a/chapter12/docs/lattice.svg b/chapter12/docs/lattice.svg deleted file mode 100644 index 41804d92..00000000 --- a/chapter12/docs/lattice.svg +++ /dev/null @@ -1,732 +0,0 @@ - - - - - - -Lattice - - -cluster_control - -Control - - -cluster_integer - -Integer - - -cluster_float - -Float - - -cluster_pointer - -Pointer = nullability × struct type - - -cluster_struct - -Struct -named collection of named scalar types - - -cluster_memory - -Memory -precise alias slices - - -cluster_tuple - -Tuple -collection of types - - - -TOP - -⊤ - - - -XCTRL - -⊤:ctrl - - - -TOP->XCTRL - - - - - -INT_TOP - -⊤:int - - - -TOP->INT_TOP - - - - - -FLT_TOP - -⊤ -:flt - - - -TOP->FLT_TOP - - - - - -PTR_TOP - -⊤:ptr -[*⊤:struct] - - - -TOP->PTR_TOP - - - - - -STRUCT_TOP - -⊤:struct -[struct top] - - - -TOP->STRUCT_TOP - - - - - -MEM_TOP - -⊤:mem - - - -TOP->MEM_TOP - - - - - -TUPLE_TOP - -⊤:tuple - - - -TOP->TUPLE_TOP - - - - - -BOT - -⊥ - - - -CTRL - -⊥:ctrl - - - -XCTRL->CTRL - - - - - - -CTRL->BOT - - - - - -INT_NEG - -… −2, −1 - - - -INT_TOP->INT_NEG - - - - - -INT_ZERO - -0 - - - -INT_TOP->INT_ZERO - - - - - -INT_POS - -1, 2, … - - - -INT_TOP->INT_POS - - - - - - -INT_BOT - -⊥:int - - - -INT_BOT->BOT - - - - - -INT_NEG->INT_BOT - - - - - - -INT_ZERO->INT_BOT - - - - - - -INT_POS->INT_BOT - - - - - -FLT_MINDOTS - -… negative constants - - - -FLT_TOP->FLT_MINDOTS - - - - - -FLT_ZERO - -0.0 - - - -FLT_TOP->FLT_ZERO - - - - - -FLT_MAXDOTS - -positive constants … - - - -FLT_TOP->FLT_MAXDOTS - - - - - - -FLT_BOT - -⊥ -:flt - - - -FLT_BOT->BOT - - - - - -FLT_MINDOTS->FLT_BOT - - - - - - -FLT_ZERO->FLT_BOT - - - - - - -FLT_MAXDOTS->FLT_BOT - - - - - -PTR_NULL - -null - - - -PTR_TOP->PTR_NULL - - - - - -PTR_PERSON - -*Person - - - -PTR_TOP->PTR_PERSON - - - - - -PTR_S1 - -*s1 - - - -PTR_TOP->PTR_S1 - - - - - -PTR_MORE - -*s2, … - - - -PTR_TOP->PTR_MORE - - - - - - -PTR_BOT - -⊥:ptr -[*⊥:struct?] - - - -PTR_BOT->BOT - - - - - -PTR_PERSON_NIL - -*Person? - - - -PTR_NULL->PTR_PERSON_NIL - - - - - -PTR_S1_NIL - -*s1? - - - -PTR_NULL->PTR_S1_NIL - - - - - -PTR_MORE_NIL - -*s2?, … - - - -PTR_NULL->PTR_MORE_NIL - - - - - -PTR_ANY - -*⊥:struct -[ptr any struct] - - - -PTR_ANY->PTR_BOT - - - - - - -PTR_PERSON->PTR_ANY - - - - - - -PTR_PERSON->PTR_PERSON_NIL - - - - - -PTR_S1->PTR_ANY - - - - - - -PTR_S1->PTR_S1_NIL - - - - - - -PTR_MORE->PTR_ANY - - - - - -PTR_MORE->PTR_MORE_NIL - - - - - -PTR_PERSON_NIL->PTR_BOT - - - - - - -PTR_S1_NIL->PTR_BOT - - - - - - -PTR_MORE_NIL->PTR_BOT - - - - - -STRUCT_PERSON - -Person -age: int - - - -STRUCT_TOP->STRUCT_PERSON - - - - - -STRUCT_S1 - -s1 -x: int - - - -STRUCT_TOP->STRUCT_S1 - - - - - -STRUCT_S2 - -s2 -x: int, y: flt - - - -STRUCT_TOP->STRUCT_S2 - - - - - -STRUCT_MORE - -… - - - -STRUCT_TOP->STRUCT_MORE - - - - - - -STRUCT_BOT - -⊥:struct -[struct bottom] - - - -STRUCT_BOT->BOT - - - - - -STRUCT_PERSON->STRUCT_BOT - - - - - - -STRUCT_S1->STRUCT_BOT - - - - - - -STRUCT_S2->STRUCT_BOT - - - - - - -STRUCT_MORE->STRUCT_BOT - - - - - -MEM_ALIAS2 - -MEM#2 - - - -MEM_TOP->MEM_ALIAS2 - - - - - -MEM_ALIAS3 - -MEM#3 - - - -MEM_TOP->MEM_ALIAS3 - - - - - -MEM_MORE - -… - - - -MEM_TOP->MEM_MORE - - - - - - -MEM_BOT - -⊥:mem - - - -MEM_BOT->BOT - - - - - -MEM_ALIAS2->MEM_BOT - - - - - - -MEM_ALIAS3->MEM_BOT - - - - - - -MEM_MORE->MEM_BOT - - - - - -TUPLE1 - - - -tuple - - -type - - - -TUPLE_TOP->TUPLE1 - - - - - -TUPLE2 - - - -tuple - - -type - - -type - - - -TUPLE_TOP->TUPLE2 - - - - - -TUPLEN - - - -tuple - - -type - - -… - - -type - - - -TUPLE_TOP->TUPLEN - - - - - -TUPLE_BOT - -⊥:tuple - - - -TUPLE_BOT->BOT - - - - - -TUPLE1->TUPLE_BOT - - - - - - -TUPLE2->TUPLE_BOT - - - - - - -TUPLEN->TUPLE_BOT - - - - - diff --git a/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java deleted file mode 100644 index 74b58e6b..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ /dev/null @@ -1,271 +0,0 @@ - package com.seaofnodes.simple; - -import com.seaofnodes.simple.IterPeeps.WorkList; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.*; - -public abstract class GlobalCodeMotion { - - // Arrange that the existing isCFG() Nodes form a valid CFG. The - // Node.use(0) is always a block tail (either IfNode or head of the - // following block). There are no unreachable infinite loops. - public static void buildCFG( StopNode stop ) { - schedEarly(); - Parser.SCHEDULED = true; - schedLate(stop); - } - - // ------------------------------------------------------------------------ - // Backwards walk on the CFG only, looking for unreachable code - which has - // to be an infinite loop. Insert a bogus never-taken exit to Stop, so the - // loop becomes reachable. Also, set loop nesting depth - static void fixLoops(StopNode stop) { - // Backwards walk from Stop, looking for unreachable code - BitSet visit = new BitSet(); - HashSet unreach = new HashSet<>(); - unreach.add(Parser.START); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - if( unreach.isEmpty() ) return; - - // Forwards walk from unreachable, looking for loops with no exit test. - visit.clear(); - for( CFGNode cfg : unreach ) - walkInfinite(cfg,visit,stop); - // Set loop depth on remaining graph - unreach.clear(); - visit.clear(); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - assert unreach.isEmpty(); - } - - - // Forwards walk over previously unreachable, looking for loops with no - // exit test. - static private void walkInfinite( CFGNode n, BitSet visit, StopNode stop ) { - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - if( n instanceof LoopNode loop ) - loop.forceExit(stop); - for( Node use : n._outputs ) - if( use instanceof CFGNode cfg ) - walkInfinite(cfg,visit,stop); - } - - // ------------------------------------------------------------------------ - private static void schedEarly() { - ArrayList rpo = new ArrayList<>(); - BitSet visit = new BitSet(); - _rpo_cfg(Parser.START, visit, rpo); - // Reverse Post-Order on CFG - for( int j=rpo.size()-1; j>=0; j-- ) { - CFGNode cfg = rpo.get(j); - cfg.loopDepth(); - for( Node n : cfg._inputs ) - _schedEarly(n,visit); - // In dead infinite loops, entire code blocks may be unreachable - // from below. Reach down from the CFG to their Phis so their - // inputs are scheduled too. - if( cfg instanceof RegionNode ) { - int len = cfg.nOuts(); - for( int i=0; i rpo) { - if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) - return; // Been there, done that - visit.set(cfg._nid); - for( Node use : cfg._outputs ) - _rpo_cfg(use,visit,rpo); - rpo.add(cfg); - } - - private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - // Schedule inputs first, except Phis: following their backedges would - // enter a data cycle before its control has been scheduled. - for( Node def : n._inputs ) - if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,visit); - // An existing edge 0 already supplies control (or a Phi/Proj binding). - if( n.in(0)==null ) { - // Schedule at deepest input - CFGNode early = Parser.START; // Maximally early, lowest idepth - for( int i=1; i early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); // First place this can go - } - } - - // ------------------------------------------------------------------------ - private static void schedLate( StopNode stop) { - CFGNode[] late = new CFGNode[Node.UID()]; - Node[] ns = new Node[Node.UID()]; - // Record Load NIDs at all their CFG block choices, then check against - // Store block choices to force a Load above an anti-dependent Store. - int[] anti = new int[Node.UID()]; - // Breadth-first scheduling - breadth(stop,ns,late,anti); - - // Copy the best placement choice into the control slot - for( int i=0; i work = new WorkList<>(); - work.push(stop); - Node n; - outer: - while( (n = work.pop()) != null ) { - assert late[n._nid]==null; // No double visit - // These I know the late schedule of, and need to set early for loops - if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); - else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); - // These nodes have a fixed late placement at their original control. - else if( n instanceof ProjNode || n instanceof NewNode || n==Parser.ZERO ) late[n._nid] = n.cfg0(); - else { - - // All uses done? - for( Node use : n._outputs ) - if( use!=null && late[use._nid]==null ) - continue outer; // Nope, await all uses done - - // Loads need their memory inputs' uses also done - if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( antiUse(ld,memuse) && late[memuse._nid]==null ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late,anti); - } - - // A use just finished; reconsider its inputs and waiting loads, - // even when the shared memory input was already scheduled. - for( Node def : n._inputs ) { - if( def==null ) continue; - if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); - } - if( n instanceof LoopNode loop ) - for( Node phi : loop._outputs ) - if( phi instanceof PhiNode && late[phi._nid]==null ) - work.push(phi); - } - } - - private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { - // Walk uses, gathering the LCA (Least Common Ancestor) of uses - CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); - assert early != null; - CFGNode lca = null; - for( Node use : n._outputs ) - if( use != null ) - lca = use_block(n,use, late).domLCA(lca,null); - - // Loads may need anti-dependencies, raising their LCA - if( n instanceof LoadNode load ) - lca = find_anti_dep(lca,load,early,late,anti); - - // Walk up from the LCA to the early, looking for best place. This is - // the lowest execution frequency, approximated by least loop depth and - // deepest control flow. - CFGNode best = lca; - lca = lca.idom(); // Already found best for starting LCA - for( ; lca != early.idom(); lca = lca.idom() ) - if( better(lca,best) ) - best = lca; - assert !(best instanceof IfNode); - ns [n._nid] = n; - late[n._nid] = best; - } - - // Block of use. Normally from late[] schedule, except for Phis, which go - // to the matching Region input. - private static CFGNode use_block(Node n, Node use, CFGNode[] late) { - if( !(use instanceof PhiNode phi) ) - return late[use._nid]; - CFGNode found=null; - for( int i=1; i best.idepth() || - best instanceof IfNode; - } - - // Only a store or memory Phi covering this alias can constrain a load. - // MemMerge packages slices without overwriting them. - private static boolean antiUse(LoadNode load, Node use) { - return switch( use ) { - case StoreNode st -> st._alias==load._alias; - case MemPhiNode phi -> phi._alias==load._alias; - case BulkMemPhiNode phi -> !phi.isSplit(load._alias); - default -> false; - }; - } - - private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { - // We could skip final-field loads here. - // Walk LCA->early, flagging Load's block location choices - for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) - anti[cfg._nid] = load._nid; - // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { - if( !antiUse(load,mem) ) continue; - switch( mem ) { - case StoreNode st: - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case PhiNode phi: - // Repeat anti-dep for matching Phi inputs. - // No anti-dep edges but may raise the LCA. - for( int i=1; i { private static final com.seaofnodes.print.IRPrinter PRINT = new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } + @Override public int inputColumns() { return 3; } @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } @Override public Kind kind(Node n) { if( n instanceof StartNode ) return Kind.START; @@ -17,15 +18,13 @@ public final class IRPrinter extends IRAdapter { if( n instanceof PhiNode ) return Kind.PHI; if( n instanceof ReturnNode ) return Kind.RETURN; if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof CProjNode ) return Kind.CPROJ; - if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ; - if( n instanceof CFGNode ) return Kind.CTRL; + if( n instanceof ProjNode ) return n.isCFG() ? Kind.CPROJ : Kind.PROJ; + if( n.isCFG() ) return Kind.CTRL; if( n instanceof MultiNode ) return Kind.MULTI; return Kind.DATA; } @Override public int index(Node n) { if( n instanceof ProjNode p ) return p._idx; - if( n instanceof CProjNode p ) return p._idx; return n._nid; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java index 65a5e64f..7075d526 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -98,10 +98,6 @@ public abstract class IterPeeps { if( obs != null ) obs.after(n, x, true); } - // Break infinite loops, forcing a Never-branch to exit - GlobalCodeMotion.fixLoops(stop); - - 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 9c213165..0fa7ee0a 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java @@ -31,18 +31,12 @@ public class Parser { */ public static StartNode START; - public static ConstantNode ZERO; - public static XCtrlNode XCTRL; - public StopNode STOP; // Field identities are independent of the memory graph and Start's tuple. private final HashMap _aliases = new HashMap<>(); private int _alias = 2; - // Debugger Printing. - public static boolean SCHEDULED; // True if debug printer can use schedule info - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; @@ -68,7 +62,6 @@ public class Parser { add("continue"); add("else"); add("false"); - add("flt"); add("if"); add("int"); add("new"); @@ -89,7 +82,7 @@ public class Parser { ScopeNode _continueScope; ScopeNode _breakScope; - // Mapping from a type name to a Type. + // Authoritative declarations, indexed by type name. public static HashMap TYPES = new HashMap<>(); public Parser(String source, TypeInteger arg) { @@ -98,15 +91,11 @@ public class Parser { IterPeeps.reset(); TYPES.clear(); TYPES.put("int",TypeInteger.BOT); - TYPES.put("flt",TypeFloat .BOT); - SCHEDULED = false; _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); STOP = new StopNode(source); - ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); - XCTRL= new XCtrlNode().peephole().keep(); } public Parser(String source) { @@ -129,8 +118,8 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); parseBlock(); if( ctrl()._type==Type.CONTROL ) @@ -190,7 +179,7 @@ public class Parser { * type IDENTIFIER ; * */ - private Field parseField(String sname) { + private Field parseField() { Type t = type(); if( t==null ) throw errorSyntax("Requires a field type, found '"+_lexer.getAnyNextToken()+"'"); @@ -198,20 +187,21 @@ public class Parser { } /** - * Parse a struct declaration, and return the following statement. + * Parse a complete struct declaration. * Only allowed in top level scope. * Structs cannot be redefined. * - * @return The statement following the struct + * @return null */ private Node parseStruct() { if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); String typeName = requireId(); - if ( TYPES.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + Type t = TYPES.get(typeName); + if( t!=null && !(t instanceof TypeStruct ts && ts._fields==null) ) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); ArrayList fields = new ArrayList<>(); require("{"); while (!peek('}') && !_lexer.isEOF()) { - Field field = parseField(typeName); + Field field = parseField(); if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); fields.add(field); } @@ -221,7 +211,7 @@ public class Parser { TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names _aliases.put(typeName,_alias); _alias += ts._fields.length; - return parseStatement(); + return null; } /** @@ -262,8 +252,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); // Clone the body Scope to create the break/exit Scope which accounts for any // side effects in the predicate. The break/exit Scope will be the final @@ -309,7 +299,7 @@ public class Parser { private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth while( cur._scopes.size() > _breakScope._scopes.size() ) @@ -345,8 +335,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current int ndefs = _scope.nIns(); @@ -363,8 +353,8 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ifF.unkeep(); // fScope already owns the false control + _scope.upcast(ifF,pred,true); // Up-cast predicate, also without an else if (matchx("else")) { - _scope.upcast(ifF,pred,true); // Up-cast predicate parseStatement(); fScope = _scope; } @@ -393,7 +383,7 @@ public class Parser { private Node parseReturn() { var expr = require(parseExpression(), ";"); Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); - ctrl(XCTRL); // Kill control + ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control return ret; } @@ -436,12 +426,13 @@ public class Parser { throw error("Undefined name '" + name + "'"); t = _scope.lookupDeclaredType(name); } - // Auto-widen int to float - if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) - expr = new ToFloatNode(expr).peephole(); + // Auto-deepen forward ref types + Type e = expr._type; + if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null ) + e = tmp.make_from((TypeStruct)TYPES.get(tmp._obj._name)); // Type is sane - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + if( !e.isa(t) ) + throw error("Type " + e.str() + " is not of declared type " + t.str()); return _scope.update(name,expr); } @@ -450,16 +441,22 @@ public class Parser { int old = _lexer._position; String tname = _lexer.matchId(); if( tname==null ) return null; + boolean nullable = match("?"); Type t = TYPES.get(tname); + // Assume a forward-reference type if( t == null ) { - // Not a type; unwind the parse - _lexer._position = old; - return null; + int old2 = _lexer._position; + String id = _lexer.matchId(); + if( id==null ) { + _lexer._position = old; + return null; + } + TYPES.put(tname,t = TypeStruct.make(tname)); + _lexer._position = old2; // Reparse ID in caller } - return t instanceof TypeStruct obj ? TypeMemPtr.make(obj,match("?")) : t; + return t instanceof TypeStruct obj ? TypeMemPtr.make(obj,nullable) : t; } - /** * Parse an expression of the form: * @@ -493,7 +490,7 @@ public class Parser { else break; // Peepholes can fire, but lhs is already "hooked", kept alive lhs.setDef(idx,parseAddition()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); if( negate ) // Extra negate for != lhs = new NotNode(lhs).peephole(); } @@ -516,7 +513,7 @@ public class Parser { else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); } return lhs; } @@ -530,14 +527,14 @@ public class Parser { * @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { - var lhs = parseUnary(); boolean mul; + var lhs = parseUnary(); while( true ) { if( false ) ; else if( match("*") ) lhs = new MulNode(lhs,null); else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); } return lhs; } @@ -551,7 +548,7 @@ public class Parser { * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { - if (match("-")) return new MinusNode(parseUnary()).widen().peephole(); + if (match("-")) return new MinusNode(parseUnary()).peephole(); if (match("!")) return new NotNode(parseUnary()).peephole(); return parsePostfix(parsePrimary()); } @@ -565,15 +562,16 @@ public class Parser { * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { - if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); + if( _lexer.isNumber(_lexer.peek()) ) return parseIntegerLiteral(); if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); - if( matchx("false") ) return ZERO; + if( matchx("false") ) return new ConstantNode(TypeInteger.ZERO).peephole(); if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); if( matchx("new") ) { String structName = requireId(); Type t = TYPES.get(structName); - if( t == null || !(t instanceof TypeStruct obj) ) throw errorSyntax("Unknown struct type '" + structName + "'"); + if( !(t instanceof TypeStruct obj) || obj._fields==null ) + throw error("Unknown struct type '" + structName + "'"); return newStruct(obj); } String name = _lexer.matchId(); @@ -590,7 +588,7 @@ public class Parser { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); int alias = _aliases.get(obj._name); for( Field field : obj._fields ) { - store(field._fname,alias,n,ZERO); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } return n.unkeep(); @@ -600,9 +598,9 @@ public class Parser { private Node mem() { return _scope.lookup("$mem"); } private Node mem(Node n) { return _scope.update("$mem",n); } - private void store(String name, int alias, Node ptr, Node val) { + private void store(String name, int alias, Type glb, Node ptr, Node val, boolean init) { Node prior = mem().keep(); - Node st = new StoreNode(name,alias,ctrl(),prior,ptr,val).peephole(); + Node st = new StoreNode(name,alias,glb,null,prior,ptr,val,init).peephole(); mem(new MemMergeNode(prior,alias,st).peephole()); prior.unkeep(); } @@ -623,7 +621,9 @@ public class Parser { String name = requireId().intern(); if( expr._type == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null"); - int idx = ptr._obj==null ? -1 : ptr._obj.find(name); + if( ptr._obj == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name); + int idx = base==null ? -1 : base.find(name); if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); int alias = _aliases.get(ptr._obj._name)+idx; @@ -632,13 +632,14 @@ public class Parser { if( peek('=') ) _lexer._position--; else { Node val = parseExpression(); - store(name,alias,expr,val); + Type glb = base._fields[idx]._type; + store(name,alias,glb,expr,val,false); return expr; // "obj.a = expr" returns the expression while updating memory } } - Type declaredType = ptr._obj._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType, mem(), expr).peephole()); + Type declaredType = base._fields[idx]._type; + return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); } /** @@ -646,10 +647,9 @@ public class Parser { * *

      *     integerLiteral: [1-9][0-9]* | [0]
-     *     floatLiteral: [1-9][0-9]* | [0]
      * 
*/ - private ConstantNode parseLiteral() { + private ConstantNode parseIntegerLiteral() { return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); } @@ -744,16 +744,7 @@ public class Parser { * Return the next non-white-space character */ private void skipWhiteSpace() { - while( true ) { - if( isWhiteSpace() ) _position++; - // Skip // to end of line - else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '/') { - _position += 2; - while( !isEOF() && _input[_position] != '\n' ) _position++; - } else break; - } + while (isWhiteSpace()) _position++; } @@ -804,37 +795,19 @@ public class Parser { return String.valueOf(peek()); } - + boolean isNumber() {return isNumber(peek());} boolean isNumber(char ch) {return Character.isDigit(ch);} - // Return a constant Type, either TypeInteger or TypeFloat private Type parseNumber() { - int old = _position; - int len = isLongOrDouble(); - if( len > 0 ) { - if( len > 1 && _input[old]=='0' ) - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(new String(_input,old,len))); - } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); + String snum = parseNumberString(); + if (snum.length() > 1 && snum.charAt(0) == '0') + throw error("Syntax error: integer values cannot start with '0'"); + return TypeInteger.constant(Long.parseLong(snum)); } private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - _position += len; - return new String(_input,old,len); - } - - // Return +len that ends a long - // Return -len that ends a double - private int isLongOrDouble() { - int old = _position; - char c; - while( Character.isDigit(c=nextChar()) ) ; - if( !(c=='e' || c=='.') ) - return --_position - old; - while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; - return -(--_position - old); + int start = _position; + while (isNumber(nextChar())) ; + return new String(_input, start, --_position - start); } // First letter of an identifier @@ -853,9 +826,8 @@ public class Parser { return new String(_input, start, --_position - start); } - // private boolean isPunctuation(char ch) { - return "=;[]<>()+-/*".indexOf(ch) != -1; + return "=;[]<>(){}+-/*!".indexOf(ch) != -1; } private String parsePunctuation() { diff --git a/chapter12/src/main/java/com/seaofnodes/simple/Utils.java b/chapter12/src/main/java/com/seaofnodes/simple/Utils.java index f3ac25b1..4f2af769 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/Utils.java @@ -55,4 +55,11 @@ public class Utils { // Fold a 64bit hash into 32 bits public static int fold( long x ) { return (int)((x>>32) ^ x); } + + public static boolean eq(String s0, String s1) { + if( s0==s1 ) return true; + assert s0==s0.intern(); + assert s1==s1.intern(); + return false; + } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index fd713156..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class AddFNode extends Node { - public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override protected String format() { return "(%1+%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()+i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Add of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} 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 01aa209f..e993a20e 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -103,7 +103,6 @@ public class AddNode extends Node { static Node phiCon(Node op, boolean rotate) { Node lhs = op.in(1); Node rhs = op.in(2); - if( rhs._type==TypeInteger.TOP ) return null; // LHS is either a Phi of constants, or another op with Phi of constants PhiNode lphi = pcon(lhs,op); if( rotate && lphi==null && lhs.nIns() > 2 ) { @@ -169,5 +168,9 @@ public class AddNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } + @Override public String err() { + if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; + if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; + return null; + } } 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 e515a5f1..63452dba 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -2,11 +2,8 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeMemPtr; @@ -27,21 +24,19 @@ abstract public class BoolNode extends Node { @Override public Type compute() { - if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); - } - if( in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1 ) { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); } return TypeInteger.BOT; } - boolean doOp(long lhs, long rhs) { throw Utils.TODO(); } - boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } + abstract boolean doOp(long lhs, long rhs); @Override public Node idealize() { @@ -53,9 +48,11 @@ abstract public class BoolNode extends Node { if( this instanceof EQ ) { if( !(in(2) instanceof ConstantNode) ) { // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) + if( in(1) instanceof ConstantNode ) + return new EQ(in(2),in(1)); // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); + else if( in(1)._nid > in(2)._nid ) + return new EQ(in(2),in(1)); } // Equals X==0 becomes a !X if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) @@ -71,12 +68,7 @@ abstract public class BoolNode extends Node { return null; } - public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } Node copyF() { return new EQF(null,null); } } - public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } Node copyF() { return new LTF(null,null); }} - public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } Node copyF() { return new LEF(null,null); } } - - public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } Node copyF() { return null; } } - public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } Node copyF() { return null; } } - public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } Node copyF() { return null; } } - + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } + public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index c690a83c..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; - -import java.util.BitSet; -import java.util.HashSet; -import java.util.HashMap; - -/** Control Flow Graph Nodes - *

- * CFG nodes have a immediate dominator depth (idepth) and a loop nesting - * depth(loop_depth). - *

- * idepth is computed lazily upon first request, and is valid even in the - * Parser, and is used by peepholes during parsing and afterward. - *

- * loop_depth is computed after optimization as part of scheduling. - * - */ -public abstract class CFGNode extends Node { - - public CFGNode(Node... nodes) { super(nodes); } - - @Override public boolean isCFG() { return true; } - - public CFGNode cfg(int idx) { return (CFGNode)in(idx); } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return false; } - - // Should be exactly 1 tail from a block head - public CFGNode blockTail() { - assert blockHead(); - for( Node n : _outputs ) - if( n instanceof CFGNode cfg ) - return cfg; - return null; - } - - // ------------------------------------------------------------------------ - /** - * Immediate dominator tree depth, used to approximate a real IDOM during - * parsing where we do not have the whole program, and also peepholes - * change the CFG incrementally. - *

- * See {@link ...} - */ - public char _idepth; - public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - return _idepth = (char)depth; - } - - - // Return the immediate dominator of this Node and compute dom tree depth. - public CFGNode idom(Node dep) { return cfg(0); } - public final CFGNode idom() { return idom(null); } - - // Return the LCA of two idoms - public CFGNode domLCA(CFGNode rhs, Node dep) { - if( rhs==null ) return this; - CFGNode lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : (CFGNode)lhs.addDep(dep)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : (CFGNode)rhs.addDep(dep)).idom(); - } - return lhs; - } - - // Loop nesting depth - public int _loopDepth; - public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(0).loopDepth()) : _loopDepth; } - - // ------------------------------------------------------------------------ - // Support routines for Global Code Motion - - // Tik-tok recursion pattern. This method is final, and every caller does - // this work. - public final void walkUnreach( BitSet visit, HashSet unreach ) { - if( visit.get(_nid) ) return; - visit.set(_nid); - _walkUnreach(visit,unreach); - unreach.remove(this); // Since we reached here... Node was not unreachable - } - - // Tik-tok recursion pattern; not-final; callers override this. - void _walkUnreach( BitSet visit, HashSet unreach ) { - cfg(0).walkUnreach(visit,unreach); - } -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index ff5d1a76..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; - -public class CProjNode extends CFGNode { - - // Which slice of the incoming multipart value - public final int _idx; - - // Debugging label - public final String _label; - - public CProjNode(Node ctrl, int idx, String label) { - super(ctrl); - _idx = idx; - _label = label; - } - - @Override public String label() { return _label; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p(_label); } - - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean blockHead() { return true; } - - public CFGNode ctrl() { return cfg(0); } - - @Override - public Type compute() { - Type t = ctrl()._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if ( ctrl() instanceof IfNode iff ) { - if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) - return iff.in(0); // We become our input control - - // Flip a negating if-test, to remove the not - if( iff.pred().addDep(this) instanceof NotNode not ) - return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - } - return null; - } - - @Override - public Node getBlockStart() { - return ctrl() instanceof IfNode ? this : ctrl().getBlockStart(); - } - @Override - boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } - - @Override - int hash() { return _idx; } - -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 9ecd1152..426736ed 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -38,7 +38,6 @@ public class ConstantNode extends Node { } @Override public boolean isMultiTail() { return true; } - // The special forever-zero is pinned, all others can sink @Override public Type compute() { return _con; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index a3f4318d..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class DivFNode extends Node { - public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()/i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Div of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } -} 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 cd52d117..d7244ace 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -37,5 +37,4 @@ public class DivNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/IfNode.java index 70340f7f..45f695a5 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -5,10 +5,8 @@ import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeTuple; -import java.util.BitSet; -import java.util.HashSet; -public class IfNode extends CFGNode implements MultiNode { +public class IfNode extends MultiNode { public IfNode(Node ctrl, Node pred) { super(ctrl, pred); @@ -20,6 +18,7 @@ public class IfNode extends CFGNode implements MultiNode { @Override protected String format() { return "if( %1 )"; } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } public Node ctrl() { return in(0); } @@ -49,20 +48,11 @@ public class IfNode extends CFGNode implements MultiNode { // Hunt up the immediate dominator tree. If we find an identical if // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { + for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); return this; } return null; } - - @Override void _walkUnreach( BitSet visit, HashSet unreach ) { - for( Node proj : _outputs ) - if( ((CProjNode)proj)._loopDepth == 0 ) - unreach.add((CProjNode)proj); - super._walkUnreach(visit,unreach); - } - - @Override public Node getBlockStart() { return ctrl().getBlockStart(); } } 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 01a82a79..b2ee6939 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -14,7 +14,6 @@ import com.seaofnodes.simple.type.Field; */ public class LoadNode extends MemOpNode { - Type _declaredType; /** * Load a value from a ptr.field. * @@ -23,12 +22,11 @@ public class LoadNode extends MemOpNode { * @param memPtr The ptr to the struct from where we load a field */ public LoadNode(String name, int alias, Type glb, Node memSlice, Node memPtr) { - super(name, alias, null, memSlice, memPtr); - _declaredType = glb; + super(name, alias, glb, null, memSlice, memPtr); } @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + return super.canDrop(other,dep) && !clobbered(dep); } // Check only immediate memory users. Stores clobber memory; Phis and @@ -67,7 +65,7 @@ public class LoadNode extends MemOpNode { // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && ptr() == st.ptr() && _alias==st._alias ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect + assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect return st.val(); } @@ -83,13 +81,9 @@ public class LoadNode extends MemOpNode { if( profit(memphi,2) || // Else must not be a loop to count profit on LHS. (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - if( ptr() instanceof NewNode || !(ptr() instanceof CastNode) ) { - if( !(ptr() instanceof NewNode) ) throw Utils.TODO(); // Validate - - Node ld1 = new LoadNode(_name,_alias,_declaredType,memphi.in(1),ptr()).peephole(); - Node ld2 = new LoadNode(_name,_alias,_declaredType,memphi.in(2),ptr()).peephole(); - return new PhiNode(_name,_type,memphi.region(),ld1,ld2); - } + Node ld1 = new LoadNode(_name,_alias,_declaredType,memphi.in(1),ptr()).peephole(); + Node ld2 = new LoadNode(_name,_alias,_declaredType,memphi.in(2),ptr()).peephole(); + return new PhiNode(_name,_type,memphi.region(),ld1,ld2); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/LoopNode.java index a0e988dd..a0ba973e 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -1,17 +1,15 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; + import java.util.BitSet; -import java.util.HashSet; public class LoopNode extends RegionNode { + public LoopNode( Node entry ) { super(null,entry,null); } - public CFGNode entry() { return cfg(1); } - public CFGNode back () { return cfg(2); } + Node entry() { return in(1); } + Node back () { return in(2); } @Override public String label() { return "Loop"; } @@ -27,54 +25,7 @@ public class LoopNode extends RegionNode { return inProgress() ? null : super.idealize(); } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public CFGNode idom(Node dep) { return entry(); } - - @Override public int loopDepth() { - if( _loopDepth!=0 ) return _loopDepth; // Was already set - _loopDepth = entry()._loopDepth+1; // Entry depth plus one - // One-time tag loop exits - for( CFGNode idom = back(); idom!=this; idom = idom.idom() ) { - // Walk idom in loop, setting depth - idom._loopDepth = _loopDepth; - // Loop exit hits the CProj before the If, instead of jumping from - // Region directly to If. - if( idom instanceof CProjNode proj ) { - assert proj.in(0) instanceof IfNode; // Loop exit test - // Find the loop exit CProj, and set loop_depth - for( Node use : proj.in(0)._outputs ) - if( use instanceof CProjNode proj2 && proj2 != idom ) - proj2._loopDepth = _loopDepth-1; - } - } - return _loopDepth; - } - - // If this is an unreachable loop, it may not have an exit. If it does not - // (i.e., infinite loop), force an exit to make it reachable. - public void forceExit( StopNode stop ) { - // Walk the backedge, then immediate dominator tree util we hit this - // Loop again. If we ever hit a CProj from an If (as opposed to - // directly on the If) we found our exit. - CFGNode x = back(); - while( x != this ) { - if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { - Node other = iff.cproj(1-exit._idx); - if( !(other.out(0) instanceof LoopNode loop) || loop.entry()==exit ) - return; // Found an exit, not an infinite loop - } - x = x.idom(); - } - // Found a no-exit loop. Insert an exit - NeverNode iff = new NeverNode(back()); - for( Node use : _outputs ) - if( use instanceof PhiNode phi ) - iff.addDef(use); - CProjNode t = new CProjNode(iff,0,"True" ); - CProjNode f = new CProjNode(iff,1,"False"); - setDef(2,f); - stop.addDef(new ReturnNode(t,Parser.ZERO,null)); - } + // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) + @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } + @Override Node idom() { return entry(); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 5a57a8c8..7c6afb0c 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -11,11 +11,14 @@ public abstract class MemOpNode extends Node { public final String _name; public final int _alias; + public final Type _declaredType; - public MemOpNode(String name, int alias, Node... nodes) { + + public MemOpNode(String name, int alias, Type glb, Node... nodes ) { super(nodes); _name = name; _alias = alias; + _declaredType = glb; } public Node mem() { return in(1); } @@ -25,7 +28,7 @@ public abstract class MemOpNode extends Node { // The caller has already checked the opcode, input shape and control. boolean canDrop(MemOpNode other, Node dep) { ptr().addDep(dep); - return _alias==other._alias && err()==null; + return _alias==other._alias && _declaredType==other._declaredType && err()==null; } @Override diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 1b45c7c5..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0) - return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; - return TypeFloat.BOT; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusFNode minus ) - return minus.in(1); - - return null; - } -} 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 ae450b22..5b65b3fb 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -27,5 +27,4 @@ public class MinusNode extends Node { return null; } - @Override Node copyF() { return new MinusFNode(null); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 6a57f3c5..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MulFNode extends Node { - public MulFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "MulF"; } - - @Override protected String format() { return "(%1*%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()*i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); } -} 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 2c13fd0f..b0359a52 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -51,5 +51,9 @@ public class MulNode extends Node { return null; } @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } - @Override Node copyF() { return new MulFNode(null,null); } + @Override public String err() { + if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; + if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; + return null; + } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MultiNode.java index ac9c0045..2743bd19 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,19 +1,13 @@ package com.seaofnodes.simple.node; -public interface MultiNode extends OutNode { +public abstract class MultiNode extends Node { - // Find a projection by index - default ProjNode proj( int idx ) { - for( Node out : outs() ) - if( out instanceof ProjNode prj && prj._idx==idx ) - return prj; - return null; - } + public MultiNode(Node... inputs) { super(inputs); } // Find a projection by index - default CProjNode cproj( int idx ) { - for( Node out : outs() ) - if( out instanceof CProjNode prj && prj._idx==idx ) + ProjNode proj( int idx ) { + for( Node out : _outputs ) + if( out instanceof ProjNode prj && prj._idx==idx ) return prj; return null; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/NeverNode.java deleted file mode 100644 index 7e42ccf9..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeTuple; - - -// "Never true" for infinite loop exits -public class NeverNode extends IfNode { - public NeverNode(Node ctrl) { super(ctrl,Parser.ZERO); } - - @Override public String label() { return "Never"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p("Never"); } - - @Override public Type compute() { return TypeTuple.IF_BOTH; } - - @Override public Node idealize() { return null; } -} 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 74ba5184..857920bc 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -22,7 +22,6 @@ public class NewNode extends Node { return p.p("new ").p(_ptr._obj.str()); } - @Override public Type compute() { return _ptr; } 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 e3503a7c..999e38fd 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java @@ -8,7 +8,6 @@ import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; import java.util.*; import java.util.function.Function; @@ -18,7 +17,7 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node extends BaseNode implements OutNode, Cloneable { +public abstract class Node extends BaseNode implements Cloneable { /** * Inputs to the node. These are use-def references to Nodes. @@ -84,7 +83,6 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable */ public Node in(int i) { return _inputs.get(i); } public Node out(int i) { return _outputs.get(i); } - @Override public ArrayList outs() { return _outputs; } public int nIns() { return _inputs.size(); } @@ -92,7 +90,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable public boolean isUnused() { return nOuts() == 0; } - public CFGNode cfg0() { return (CFGNode)in(0); } + public boolean isMem() { return false; } /** * Change a def into a Node. Keeps the edges correct, by removing @@ -119,11 +117,11 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // the new_def might get killed if the old node kills it recursively. if( new_def != null ) new_def.addUse(this); - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); if( old_def != null && // If the old def exists, remove a def->use edge old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def + // Set the new_def over the old (killed) edge + _inputs.set(idx,new_def); moveDepsToWorklist(); // Return self for easy flow-coding return new_def; @@ -151,7 +149,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable * @param new_def the new definition, appended to the end of existing definitions * @return new_def for flow coding */ - public Node addDef(Node new_def) { + Node addDef(Node new_def) { unlock(); // Add use->def edge _inputs.add(new_def); @@ -271,8 +269,8 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable Type old = setType(compute()); // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && !(this instanceof XCtrlNode) && _type.isHighOrConst() ) - return (_type==Type.XCONTROL ? new XCtrlNode() : new ConstantNode(_type)).peepholeOpt(); + if( !(this instanceof ConstantNode) && _type.isHighOrConst() ) + return new ConstantNode(_type).peepholeOpt(); // Global Value Numbering if( _hash==0 ) { @@ -486,37 +484,6 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // Subclasses add extra hash info (such as ConstantNodes constant) int hash() { return 0; } - // ------------------------------------------------------------------------ - // - - /** Is this Node control-flow-graph related */ - - /** Is this Node Memory related */ - public boolean isMem() { return false; } - - /** Return block start from a isCFG() */ - public Node getBlockStart() { return null; } - - // Semantic change to the graph (so NOT a peephole), used by the Parser. - // If any input is a float, flip to a float-flavored opcode and widen any - // non-float input. - public final Node widen() { - if( !hasFloatInput() ) return this; - Node flt = copyF(); - if( flt==null ) return this; - for( int i=1; i implements OutNode, Cloneable return true; } + /** + * Immediate dominator tree depth, used to approximate a real IDOM depth + * during parsing where we do not have the whole program, and also + * peepholes change the CFG incrementally. + *

+ * See {@link ...} + */ + public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit + + // Find the lowest common ancestor in the current dominator tree. + Node domLCA(Node rhs) { + if( rhs==null ) return this; + Node lhs = this; + while( lhs != rhs ) { + if( lhs==null || rhs==null ) return null; + int comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } + + int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } + // Zero depth means uncached. Check before narrowing so overflow cannot wrap. + final int cacheIDepth(int depth) { + assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + return _idepth = (char)depth; + } + + // Return the immediate dominator of this Node. + Node idom() { return in(0); } + // Make a shallow copy (same class) of this Node, with given inputs and // empty outputs and a new Node ID. The original inputs are ignored. // Does not need to be implemented in isCFG() nodes. 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 4539f982..5e556390 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -15,10 +15,8 @@ public class NotNode extends Node { public Type compute() { Type t0 = in(1)._type; switch( t0 ) { - case TypeInteger i0: return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; - case TypeFloat i0: - if( i0==TypeFloat.TOP ) return TypeInteger.TOP; - return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : TypeInteger.BOT; + case TypeInteger i0: + return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; case TypeMemPtr p0: // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot // If input in null then true diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/OutNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/OutNode.java deleted file mode 100644 index 7e531145..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/OutNode.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.seaofnodes.simple.node; - -import java.util.ArrayList; - -public interface OutNode { - ArrayList outs(); -} 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 8dfa04cf..6995cc2f 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -32,9 +32,10 @@ public class PhiNode extends Node { return p.unchar(',').close(); } - public CFGNode region() { return (CFGNode)in(0); } + Node region() { return in(0); } @Override public boolean isMultiTail() { return true; } - @Override public boolean isMem() { return _declaredType instanceof TypeMem; } + @Override + public boolean isMem() { return _declaredType instanceof TypeMem; } @Override public Type compute() { @@ -82,11 +83,11 @@ public class PhiNode extends Node { if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; if( nullx != -1 ) { Node val = in(3-nullx); - if( region().idom(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) { + if( region().idom() instanceof IfNode iff && iff.pred().addDep(this)==val ) { // Must walk the idom on the null side to make sure we hit False. - CFGNode idom = (CFGNode)region().in(nullx); - while( idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof CProjNode proj && proj._idx==1 ) + Node idom = region().in(nullx); + while( idom.in(0) != iff ) idom = idom.idom(); + if( idom instanceof ProjNode proj && proj._idx==1 ) return val; } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ProjNode.java index e82c69b3..a2632110 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -15,7 +15,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - public ProjNode(Node ctrl, int idx, String label) { + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; _label = label; @@ -27,16 +27,30 @@ public class ProjNode extends Node { @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p(_label); } + @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; } @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean isMem() { return _type instanceof TypeMem; } + public MultiNode ctrl() { return (MultiNode)in(0); } + @Override public Type compute() { Type t = in(0)._type; return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; } - @Override public Node idealize() { return null; } + @Override + public Node idealize() { + if ( ctrl() instanceof IfNode iff ) { + if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) + return iff.in(0); // We become our input control + + // Flip a negating if-test, to remove the not + if( iff.pred().addDep(this) instanceof NotNode not ) + return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); + } + return null; + } @Override boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java index cdfd10ad..8b52494a 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -3,15 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; + import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; -import java.util.HashSet; -public class RegionNode extends CFGNode { +public class RegionNode extends Node { public RegionNode(Node... nodes) { super(nodes); } @@ -22,8 +20,8 @@ public class RegionNode extends CFGNode { return p.p(label()).p(_nid); } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } @Override public Type compute() { @@ -59,7 +57,7 @@ public class RegionNode extends CFGNode { IterPeeps.addAll(phi._outputs); } } - return isDead() ? Parser.XCTRL : delDef(path); + return isDead() ? new ConstantNode(Type.XCONTROL) : delDef(path); } // If down to a single input, become that input if( nIns()==2 && !hasPhi() ) @@ -91,30 +89,23 @@ public class RegionNode extends CFGNode { } // Immediate dominator of Region is a little more complicated. - @Override public int idepth() { + @Override int idepth() { if( _idepth!=0 ) return _idepth; int depth=0; for( Node n : _inputs ) if( n!=null ) - depth = Math.max(depth,((CFGNode)n).idepth()+1); + depth = Math.max(depth,n.idepth()+1); return cacheIDepth(depth); } - @Override public CFGNode idom(Node dep) { - CFGNode lca = null; + @Override Node idom() { + Node lca = null; // Recompute from predecessors: CFG edits can change the dominator. for( int i=1; i unreach ) { - for( int i=1; i1 && in(nIns()-1) == null; @@ -124,6 +115,4 @@ public class RegionNode extends CFGNode { @Override boolean eq( Node n ) { return !inProgress(); } - - @Override public Node getBlockStart() { return this; } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 161cc7d6..ef4ddb3a 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -6,11 +6,10 @@ import com.seaofnodes.simple.type.*; /** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ -public class ReturnNode extends CFGNode { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // A synthetic never-taken loop exit has no source scope. - super(ctrl, scope == null ? null : scope.lookup("$mem"), data); + super(ctrl, scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } @@ -27,6 +26,8 @@ public class ReturnNode extends CFGNode { return p.p(";"); } + @Override public boolean isCFG() { return true; } + @Override public Type compute() { // Return exposes the complete memory state. @@ -39,6 +40,4 @@ public class ReturnNode extends CFGNode { return ctrl(); return null; } - - @Override public Node getBlockStart() { return ctrl().getBlockStart(); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java index a5d4923f..3404f111 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,13 +3,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.*; -import java.util.BitSet; -import java.util.HashSet; -/** Start supplies control, whole memory, and the argument, in that order. */ -public class StartNode extends CFGNode implements MultiNode { - - private final TypeTuple _args; +/** The entry tuple: control, initial whole-memory state, and argument. */ +public class StartNode extends MultiNode { + final TypeTuple _args; public StartNode(Type[] args) { super(); @@ -23,8 +20,8 @@ public class StartNode extends CFGNode implements MultiNode { return p.p(label()); } + @Override public boolean isCFG() { return true; } @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } @Override public TypeTuple compute() { return _args; } @@ -33,12 +30,6 @@ public class StartNode extends CFGNode implements MultiNode { public Node idealize() { return null; } // No immediate dominator, and idepth==0 - @Override public int idepth() { return 0; } - @Override public CFGNode idom(Node dep) { return null; } - - @Override void _walkUnreach( BitSet visit, HashSet unreach ) { } - - @Override public int loopDepth() { return (_loopDepth=1); } - - @Override public Node getBlockStart() { return this; } + @Override int idepth() { return 0; } + @Override Node idom() { return null; } } 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 b5d2d716..4b4e9615 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.IterPeeps; -public class StopNode extends CFGNode { +public class StopNode extends Node { public final String _src; @@ -31,7 +31,7 @@ public class StopNode extends CFGNode { return p.p("]"); } - @Override public boolean blockHead() { return true; } + @Override public boolean isCFG() { return true; } // If a single Return, return it. // Otherwise, null because ambiguous. @@ -54,17 +54,7 @@ public class StopNode extends CFGNode { return null; } - @Override public int idepth() { - if( _idepth!=0 ) return _idepth; - int d=0; - for( Node n : _inputs ) - if( n!=null ) - d = Math.max(d,((CFGNode)n).idepth()+1); - return cacheIDepth(d); - } - @Override public CFGNode idom(Node dep) { return null; } - - @Override public int loopDepth() { return (_loopDepth=1); } + @Override public Node idom() { return null; } public Node addReturn(Node node) { return addDef(node); @@ -82,6 +72,4 @@ public class StopNode extends CFGNode { if( obs != null ) obs.phase("TypeCheck"); return this; } - - @Override public Node getBlockStart() { return this; } } 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 f61afe78..e9eaaf2e 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -13,20 +13,26 @@ import com.seaofnodes.simple.type.*; */ public class StoreNode extends MemOpNode { + private final boolean _init; // Initializing writes are allowed to write null + /** * @param name The struct field we are assigning to * @param memSlice The memory alias node - this is updated after a Store * @param memPtr The ptr to the struct where we will store a value * @param value Value to be stored */ - public StoreNode(String name, int alias, Node ctrl, Node memSlice, Node memPtr, Node value) { - super(name, alias, ctrl, memSlice, memPtr, value); + public StoreNode(String name, int alias, Type glb, Node ctrl, Node memSlice, Node memPtr, Node value, boolean init) { + super(name, alias, glb, ctrl, memSlice, memPtr, value); + _init = init; } - @Override - public String label() { return "Store"; } - @Override - public boolean isMem() { return true; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + + @Override public String label() { return "." +_name+"="; } + @Override public boolean isMem() { return true; } + public Node val() { return in(3); } @Override @@ -72,4 +78,11 @@ public class StoreNode extends MemOpNode { return false; } + @Override + String err() { + String err = super.err(); + if( err != null ) return err; + Type ptr = val()._type; + return _init || ptr.isa(_declaredType) ? null : "Cannot store "+ptr+" into field "+_declaredType+" "+_name; + } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 2d218a03..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class SubFNode extends Node { - public SubFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "SubF"; } - - @Override protected String format() { return "(%1-%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()-i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Sub of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } -} 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 f36f2af5..6fa87a07 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; @@ -28,7 +27,6 @@ public class SubNode extends Node { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(i0.value()-i1.value()); } - return TypeInteger.BOT; } @@ -57,5 +55,4 @@ public class SubNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java deleted file mode 100644 index 26cf50e1..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; - - -public class ToFloatNode extends Node { - public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } - - @Override protected String format() { return "(flt)%1"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeInteger i0 && i0.isConstant() ) - return TypeFloat.constant(i0.value()); - return TypeFloat.BOT; - } - - @Override public Node idealize() { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); } -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java deleted file mode 100644 index 056dfba1..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - -public class XCtrlNode extends CFGNode { - public XCtrlNode( ) { super(Parser.START); } - @Override public String label() { return "Xctrl"; } - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p("Xctrl"); } - @Override public Type compute() { return Type.XCONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index e65963db..92d970d8 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,13 +6,13 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 12's view of the IR; browser, transport and layout are shared. */ +/** Chapter 10's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter { @Override protected String[] edgeNames(Node n) { return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } + @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : -1; } @Override protected Kind kind(Node n) { if( n instanceof StopNode ) return Kind.STOP; @@ -25,9 +25,9 @@ public class SimpleGraphAdapter extends GraphAdapter { } @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + 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 || n instanceof CProjNode ) return n.isCFG() ? Role.CTRL : 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; diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter12/src/main/java/com/seaofnodes/simple/type/Field.java index b50a4fa6..b2c006dd 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/Field.java @@ -53,7 +53,7 @@ public class Field extends Type { @Override public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_fname).append(":")); + return _type.print(sb.append(_fname).append(" :")); } @Override public String str() { return _fname; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter12/src/main/java/com/seaofnodes/simple/type/Type.java index 45d66be4..93e27c76 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/Type.java @@ -33,12 +33,11 @@ public class Type { static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) static final byte TSIMPLE = 4; // End of the Simple Types static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TFLT = 6; // All Integers; see TypeInteger - static final byte TTUPLE = 7; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 8; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TMEMPTR = 9; // Memory pointer type - static final byte TSTRUCT =10; // Structs; tuples with named fields - static final byte TFLD =11; // Fields into struct + static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias + static final byte TMEMPTR = 8; // Memory pointer type + static final byte TSTRUCT = 9; // Structs; tuples with named fields + static final byte TFLD =10; // Fields into struct public final byte _type; diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index 738aebc9..00000000 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Float Type - */ -public class TypeFloat extends Type { - - public final static TypeFloat TOP = make(false, 0); - public final static TypeFloat BOT = make(false, 1); - public final static TypeFloat ZERO= make(true , 0); - - public final boolean _is_con; - - /** - * The constant value or - * if not constant then 1=bottom, 0=top. - */ - public final double _con; - - private TypeFloat(boolean is_con, double con) { - super(TFLT); - _is_con = is_con; - _con = con; - } - public static TypeFloat make(boolean is_con, double con) { - return new TypeFloat(is_con,con).intern(); - } - - public static TypeFloat constant(double con) { return make(true, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(_con); - } - - @Override public String str() { - if( this==TOP ) return "~flt"; - if( this==BOT ) return "flt"; - return ""+_con; - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append("Int"); - } - - @Override - public boolean isHighOrConst() { return _is_con || _con==0; } - - @Override - public boolean isConstant() { return _is_con; } - - public double value() { return _con; } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeFloat) - TypeFloat i = (TypeFloat)other; // Contract - // BOT wins - if ( this==BOT ) return this; - if ( i ==BOT ) return i ; - // TOP loses - if ( i ==TOP ) return this; - if ( this==TOP ) return i ; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return BOT; - } - - @Override - public Type dual() { - if( isConstant() ) return this; // Constants are a self-dual - return _con==0 ? BOT : TOP; - } - - @Override - public Type glb() { return BOT; } - - @Override public TypeFloat makeInit() { return ZERO; } - - @Override - int hash() { return (int)(Double.hashCode(_con) ^ (_is_con ? 0 : 0x4000)); } - @Override - public boolean eq( Type t ) { - TypeFloat i = (TypeFloat)t; // Contract - return _con==i._con && _is_con==i._is_con; - } -} diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMem.java index b6fc8824..38b3635a 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -15,7 +15,7 @@ public class TypeMem extends Type { // 0 means TOP, no slice. // -1 means BOT, all memory. // N means slice#N. - private final int _alias; + public final int _alias; private TypeMem(int alias) { super(TMEM); _alias = alias; } @@ -23,7 +23,6 @@ public class TypeMem extends Type { public static final TypeMem TOP = make( 0); public static final TypeMem BOT = make(-1); - public static void gather(ArrayList ts) { ts.add(make(1)); ts.add(BOT); } @Override @@ -40,7 +39,7 @@ public class TypeMem extends Type { } @Override - public Type glb() { return TypeMem.BOT; } + public Type glb() { return make(_alias); } @Override int hash() { return 9876543 + _alias; } @@ -62,5 +61,5 @@ public class TypeMem extends Type { }); } - @Override public String str() { return print(new StringBuilder()).toString(); } + @Override public String str() { return toString(); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 6c874a93..5caa816d 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -13,21 +13,27 @@ import java.util.ArrayList; */ public class TypeMemPtr extends Type { // A TypeMemPtr is pair (obj,nil) - // where obj is one of - // (null,TypeStruct). - // where nil is one of - // (true,false) meaning an explicit null is allowed or not + // where obj is a TypeStruct, possibly TypeStruct.BOT + // where nil is an explicit null is allowed or not - public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY + // Examples: + // (Person,false) - a not-nil Person + // (Person,true ) - a Person or a nil + // (BOT ,false) - a not-nil void* (unspecified struct) + // (TOP ,true ) - a nil + + public final TypeStruct _obj; public final boolean _nil; private TypeMemPtr(TypeStruct obj, boolean nil) { super(TMEMPTR); + assert obj!=null; _obj = obj; _nil = nil; } public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } - public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); } + public static TypeMemPtr make(TypeStruct obj) { return make(obj, false); } + public TypeMemPtr make_from(TypeStruct obj) { return make(obj, _nil); } public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); public static final TypeMemPtr TOP = BOT.dual(); @@ -43,17 +49,14 @@ public class TypeMemPtr extends Type { } @Override - public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); } + public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil); } @Override - public Type glb() { - if( _obj==null ) return BOT; - return make(_obj.glb(),true); - } + public Type glb() { return make(_obj.glb(),true); } @Override public TypeMemPtr makeInit() { return NULLPTR; } @Override - int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); } + int hash() { return _obj.hashCode() ^ (_nil ? 1024 : 0); } @Override boolean eq(Type t) { diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 44741def..02de7cc8 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -8,9 +8,12 @@ import java.util.ArrayList; */ public class TypeStruct extends Type { - // For now in terms of the lattice - // a struct type just stays as is - + // Immutable shallow references, resolved by name in Parser.TYPES when needed. + // A name-only struct has _fields == null, even after its definition is known. + // For LLI { LLI? next; int i; }, L0 is name-only and L1.next points to L0; + // TYPES["LLI"] holds L1. We deliberately stop recursive type expansion here, + // keeping interning and lattice operations finite and acyclic. Never patch + // L0 into L1: field access looks up the complete declaration by name instead. public final String _name; public final Field[] _fields; @@ -20,13 +23,23 @@ public class TypeStruct extends Type { _fields = fields; } + // Forward-ref version + public static TypeStruct make(String name) { return new TypeStruct(name, null).intern(); } + // All fields directly listed public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); } + // Parser uses an ArrayList to gather the fields public static TypeStruct make(String name, ArrayList fields) { return make(name,fields.toArray(new Field[fields.size()])); } public static final TypeStruct TOP = make("$TOP",new Field [0]); public static final TypeStruct BOT = make("$BOT",new Field [0]); public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); - public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); } + + // A pair of self-cyclic types + private static final TypeStruct S1F = make("S1"); + private static final TypeStruct S2F = make("S2"); + public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", TypeInteger.BOT), Field.make("s2",TypeMemPtr.make(S2F,false)) }); + private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeInteger.BOT), Field.make("s1",TypeMemPtr.make(S1F,false)) }); + public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); } public int find(String fname) { for( int i=0; i<_fields.length; i++ ) @@ -51,6 +64,8 @@ public class TypeStruct extends Type { // And the answer is: "don't ask". if( !_name.equals(that._name) ) return BOT; // It's a struct; that's about all we know + if( this._fields==null ) return this; + if( that._fields==null ) return that; // Now all fields should be the same, so just do field meets assert _fields.length == that._fields.length; Field[] flds = new Field[_fields.length]; @@ -63,6 +78,7 @@ public class TypeStruct extends Type { public TypeStruct dual() { if( this==TOP ) return BOT; if( this==BOT ) return TOP; + if( _fields == null ) return this; Field[] flds = new Field[_fields.length]; for( int i=0; i<_fields.length; i++ ) flds[i] = _fields[i].dual(); @@ -80,9 +96,10 @@ public class TypeStruct extends Type { return make(_name,flds); } private boolean _glb() { - for( Field f : _fields ) - if( f.glb() != f ) - return false; + if( _fields!=null ) + for( Field f : _fields ) + if( f.glb() != f ) + return false; return true; } @@ -91,6 +108,7 @@ public class TypeStruct extends Type { TypeStruct ts = (TypeStruct)t; // Invariant if( !_name.equals(ts._name) ) return false; + if( _fields == ts._fields ) return true; if( _fields.length!=ts._fields.length ) return false; for( int i = 0; i < _fields.length; i++ ) @@ -102,7 +120,9 @@ public class TypeStruct extends Type { @Override int hash() { long hash = _name.hashCode(); - for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode(); + if( _fields != null ) + for( Field f : _fields ) + hash = Utils.rot(hash,13) ^ f.hashCode(); return Utils.fold(hash); } @@ -110,11 +130,12 @@ public class TypeStruct extends Type { public StringBuilder print(StringBuilder sb) { if( this==BOT ) return sb.append("StructBot"); if( this==TOP ) return sb.append("StructTop"); - sb.append(_name).append(" {\n"); + sb.append(_name); + if( _fields == null ) return sb; // Forward reference struct, just print the name + sb.append(" {\n"); for( Field f : _fields ) { - sb.append(" ").append(f._fname).append(":"); - f._type.print(sb); - sb.append(";\n"); + sb.append(" ").append(f._fname).append(" :"); + f._type.print(sb).append(";\n"); } return sb.append("}"); } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index ec7390ca..0aa019a7 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -58,7 +58,8 @@ public class TypeTuple extends Type { for( Type t : _types ) t.typeName(sb).append(","); sb.setLength(sb.length()-1); - return sb.append("]"); + sb.append("]"); + return sb; } public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter01Test.java index c10dc510..89882b2f 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -15,7 +15,7 @@ public class Chapter01Test { StartNode start = Parser.START; ReturnNode ret = (ReturnNode)stop.in(0); - assertTrue(ret.ctrl() instanceof CProjNode); + assertTrue(ret.ctrl() instanceof ProjNode); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { assertEquals(start,con.in(0)); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 14d31245..edc62f50 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -83,7 +83,7 @@ public class Chapter04Test { Parser parser = new Parser("return arg; "); StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); - assertTrue(ret.in(0) instanceof CProjNode); + assertTrue(ret.in(0) instanceof ProjNode); assertTrue(ret.in(1) instanceof ProjNode); assertEquals(1, ((ProjNode)ret.in(1))._idx); assertTrue(ret.in(2) instanceof ProjNode); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 7beeb4d9..97cd9940 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region21,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); } @Test @@ -37,7 +37,7 @@ if (arg == 1) { } return c;""", TypeInteger.BOT); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region18,4,3);", ret.toString()); } @Test @@ -65,7 +65,7 @@ else b=a+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region35,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); } @Test @@ -100,7 +100,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,4,5));", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); } @Test @@ -116,7 +116,7 @@ if( arg==0 ) return arg+a+b; """, TypeInteger.BOT); StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region16,1,0))+Phi(Region31,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region16,1,0))+Phi(Region32,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region18,3,2));", ret.toString()); } @Test diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java index df0f2848..9af11b68 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -17,7 +17,7 @@ return 1; """); StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -46,7 +46,7 @@ return a; """); StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region40,42,5);", stop.toString()); + assertEquals("return Phi(Region38,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region33,2,5);", stop.toString()); + assertEquals("return Phi(Region31,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); + assertEquals("return Phi(Region42,3,Phi(Region40,4,5));", stop.toString()); } @Test @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region39,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region39,Phi(Region24,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 222982e7..ff0ca599 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,8 +18,8 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region25,1,Phi(Loop14,1,(Phi_a+1)));", stop.toString()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,Phi(Loop23,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop9,0,Phi(Loop21,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -71,8 +71,8 @@ while(a < 10) { return b; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop9,2,Phi(Region28,Phi_b,4));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -90,8 +90,8 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop9,2,(Phi(Region28,Phi_b,4)+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -107,8 +107,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -123,8 +123,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -136,8 +136,8 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,2);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -149,8 +149,8 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,2);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -165,8 +165,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -181,8 +181,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -198,8 +198,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -215,8 +215,8 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 8c644977..02c3a301 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; import org.junit.Assert; import org.junit.Test; @@ -23,7 +24,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region38,Phi(Region26,Phi(Loop7,arg,(Phi_arg+1)),Add),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); Assert.assertEquals(5L, Evaluator.evaluate(stop, 1)); Assert.assertEquals(10L, Evaluator.evaluate(stop, 6)); @@ -46,8 +47,8 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop8,1,Phi(Region45,Phi_a,(Phi_a+1)));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -65,7 +66,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region36,Phi(Loop7,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +82,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region26,Phi(Loop7,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,8 +101,8 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -117,8 +118,8 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); } @Test @@ -185,7 +186,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region29,Phi(Loop8,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,8 +202,8 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); + assertEquals("return (Phi(Loop8,1,Add)+1);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); } } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 49ee75a2..b1fdc459 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -5,6 +5,7 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.Ignore; @@ -69,7 +70,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +87,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +116,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop15,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -133,7 +134,7 @@ while(v1+arg) { } """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); + assertEquals("return 0;", stop.toString()); } @Test @@ -185,7 +186,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop10,0,(-(Phi_a+3)));", stop.toString()); } @Test @@ -199,7 +200,7 @@ int v0=0!=0<-0; return -0+0+0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); + assertEquals("Stop[ ]", stop.toString()); } @@ -226,7 +227,7 @@ while(1) { } """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); + assertEquals("Stop[ ]", stop.toString()); } @Test @@ -248,17 +249,16 @@ while(1) { return 0!=0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); + assertEquals("Stop[ ]", stop.toString()); } @Test public void testFuzz6() { - Parser parser = new Parser( -""" + Parser parser = new Parser(""" int v0=0; while(0==1) while(v0) v0=1+v0; -"""); + """); StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); } @@ -276,14 +276,14 @@ while(1) return v1+v0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); + assertEquals("Stop[ ]", stop.toString()); } @Test public void testFuzz8() { Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop7,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 38aceb84..55574c13 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -169,7 +169,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString()); } @Test @@ -221,7 +221,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region17,null,new Bar);", stop.toString()); + assertEquals("return Phi(Region31,(*void)Phi(Region18,null,new Bar),null);", stop.toString()); } @Test @@ -237,7 +237,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,4,3);", stop.toString()); + assertEquals("return Phi(Region37,4,3);", stop.toString()); } @Test @@ -254,8 +254,7 @@ while(arg) { return ret; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { - assertEquals("Might be null accessing 'v0'", e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); } } @Test @@ -307,12 +306,11 @@ while(arg) { v0.v0 = arg; arg = arg-1; if (arg==5) ret=v0; - } return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop11,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); } @Test @@ -329,7 +327,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop15,new s0,Phi(Region36,new s0,Phi_ret));", stop.toString()); } @@ -346,7 +344,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop11,new s0,Phi(Region33,new s0,Phi_ret));", stop.toString()); } @Test @@ -464,6 +462,7 @@ return 0; assertEquals("return 0;", stop.toString()); } + @Test public void testReadBeforeStores() { var stop = new Parser(""" struct S { int x; int y; } @@ -550,8 +549,8 @@ return 0; if (arg) s.x=arg+1; else s.x=arg+2; return s; """).parse().iterate(); - // These chapters still bind struct Stores to branch control. - assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet())); + // The initializing slice also remains live as the aggregate default. + assertEquals(2,countMemoryNodes(stop,StoreNode.class,new BitSet())); assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 6f094cfb..00000000 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; } - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 0e492591..5f92c9c1 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,532 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } - } - } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } - } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; return 0; return 0; ]", stop.toString()); - } - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region82,Phi_primeCount,Phi(Region77,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; } -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; } -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,Phi(Region39,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( - """ -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); - } - - @Test - public void testSplit() { - Parser parser = new Parser( - """ -struct S { int f; } -S s = new S; -if( arg==0 ) s.f = 1; -else if (arg == 1) s.f = 1; -return s.f; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region41,1,Phi(Region36,1,0));", stop.toString()); - assertEquals(1L, Evaluator.evaluate(stop, 0)); - assertEquals(1L, Evaluator.evaluate(stop, 1)); - assertEquals(0L, Evaluator.evaluate(stop, 2)); } } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java index e577c8c3..0d4f3c97 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -2,84 +2,199 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.TypeStruct; +import com.seaofnodes.simple.type.TypeMemPtr; +import org.junit.Ignore; import org.junit.Test; - - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); - assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + + @Test + public void testLinkedList0() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var stop = new Parser(src).parse().iterate(); - assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); - } + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( next==null ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 3)); } + @Test + public void testCoRecur() { + Parser parser = new Parser( +""" +struct Left { int i; Right? f; } +struct Right { int f; Left? i; } +Left left = new Left; +left.i = 17; +Right right = new Right; +right.f = 314; +left.f = right; +right.i = left; +return right.i.f.i.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 17;", stop.toString()); + } @Test - public void testJig() { + public void testNullReright() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return null;", stop.toString()); } @Test - public void testFloat() { + public void testNullRef1() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +n.next = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return new N;", stop.toString()); } @Test - public void testSquareRoot() { + public void testNullRef2() { Parser parser = new Parser( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct N { N next; int i; } +N n = new N; +n.next = null; +return n.next; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + } + + + @Test + public void testNullRef4() { + Parser parser = new Parser("-null-5/null-5"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test public void testNullRef5() { + Parser parser = new Parser("return null+42;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test + public void testEmpty() { + Parser parser = new Parser( +""" +struct S{}; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,(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)); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testForwardReright() { + Parser parser = new Parser( +""" +struct S1 { S2 s; } +return new S2; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { Parser parser = new Parser( """ -flt x = arg; -return x+1==x; +struct S1 { S2? s; } +struct S2 { int x; } +return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); + assertEquals("return new S1;", stop.toString()); } + @Test + public void testcheckNull() { + Parser parser = new Parser( +""" +struct I {int i;} +struct P {I pi;} +P p1 = new P; +P p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + } + + + @Test + public void testShallowRecursiveType() { + new Parser("struct LLI { LLI? next; int i; } return 0;").parse().iterate(); + TypeStruct l1 = (TypeStruct)Parser.TYPES.get("LLI"); + TypeStruct l0 = ((TypeMemPtr)l1._fields[0]._type)._obj; + assertEquals("LLI", l0._name); + assertNull(l0._fields); + assertEquals(2, l1._fields.length); + assertSame(l0, l1.meet(l0)); + assertSame(l0, l0.dual()); + assertSame(l0, l0.glb()); + } + + @Test + public void testUnusedForwardReference() { + StopNode stop = new Parser("struct Holder { Missing? ref; } Holder h = new Holder; return h.ref;") + .parse().iterate(); + assertEquals("return null;", stop.toString()); + assertNull(((TypeStruct)Parser.TYPES.get("Missing"))._fields); + } } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter12/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index 52c2a014..019140ad 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -29,8 +29,8 @@ public class FuzzerWrap { fuzzer.fuzzPeepsRegression(seed); } - @Ignore @Test + @Ignore public void fuzzPeepsLarge() { var fuzzer = new Fuzzer(); for (int i=0; i<1000000; i++) diff --git a/chapter12/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter12/src/test/java/com/seaofnodes/simple/TypeTest.java index f9c580cc..298fc6f8 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -36,9 +36,9 @@ public class TypeTest { Assert.assertEquals(TypeMem .BOT, m2.meet(m3)); Assert.assertEquals(TypeMem .BOT, m3.meet(m4)); - Assert.assertEquals(TypeMem.BOT, m1.glb()); + Assert.assertEquals(m1, m1.glb()); Assert.assertEquals(m1, m1.dual()); - Assert.assertEquals(TypeMem.TOP, m1.glb().dual()); + Assert.assertEquals(m1, m1.glb().dual()); TypeMemPtr ptr1 = TypeMemPtr.make(s1); Assert.assertEquals(s1, ptr1._obj); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 40a2f78d..ac7c5e09 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -2,7 +2,10 @@ package com.seaofnodes.simple.evaluator; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.TypeStruct; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -77,10 +80,6 @@ public class Evaluator { long in2 = vall(div.in(2)); return in2 == 0 ? 0 : vall(div.in(1)) / in2; } - private double div(DivFNode div) { - double in2 = vald(div.in(2)); - return in2 == 0 ? 0 : vald(div.in(1)) / in2; - } private Object alloc(NewNode alloc) { var type = ((TypeMemPtr)alloc.compute())._obj; @@ -116,16 +115,10 @@ public class Evaluator { if (v instanceof Number n) return n.longValue(); throw new AssertionError("Not a long " + v); } - private double vald(Node node) { - var v = val(node); - if (v instanceof Number n) return n.doubleValue(); - throw new AssertionError("Not a double " + v); - } private Object cons(ConstantNode cons) { var type = cons.compute(); if (type instanceof TypeInteger i) return i.value(); - if (type instanceof TypeFloat i) return i.value(); assert type instanceof TypeMemPtr; return null; } @@ -134,30 +127,20 @@ public class Evaluator { return switch (node) { case ConstantNode cons -> cons(cons); case AddNode add -> vall(add.in(1)) + vall(add.in(2)); - case AddFNode add -> vald(add.in(1)) + vald(add.in(2)); - case BoolNode.EQF eq -> vald(eq.in(1)) == vald(eq.in(2)) ? 1L : 0L; - case BoolNode.LEF le -> vald(le.in(1)) <= vald(le.in(2)) ? 1L : 0L; - case BoolNode.LTF lt -> vald(lt.in(1)) < vald(lt.in(2)) ? 1L : 0L; case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; case DivNode div -> div(div); - case DivFNode div -> div(div); case MinusNode minus -> -vall(minus.in(1)); - case MinusFNode minus -> -vald(minus.in(1)); case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); - case MulFNode mul -> vald(mul.in(1)) * vald(mul.in(2)); case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); - case SubFNode sub -> vald(sub.in(1)) - vald(sub.in(2)); case CastNode cast -> val(cast.in(1)); - case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); - case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; - case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; + case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; default -> throw new AssertionError("Unexpected node " + node); }; } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index d16f4300..48ac0562 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -164,7 +164,7 @@ public class Scheduler { * @return true if the node is not XCtrl. */ private static boolean isNotXCtrl(Node node) { - return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL) && !(node instanceof XCtrlNode); + return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); } /** @@ -215,7 +215,7 @@ public class Scheduler { private void refinePlacement(NodeData data, BasicBlock block) { assert !isPinnedNode(data); data.block = data.block == null ? block : dom(data.block, block); - assert isValid(data); + assert isValid(data) : data.node.uniqueName()+" at "+data.block.entry.uniqueName(); } /** @@ -328,8 +328,6 @@ public class Scheduler { for(int i=1; id.block==null).orElse(false)) return false; } - } else if (node instanceof XCtrlNode) { - return false; } else { assert d(node.in(0)).block != null; } @@ -405,7 +403,7 @@ public class Scheduler { } } if (!(node instanceof ReturnNode)) - for (Node n:node._outputs) if (n!=null && n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); + for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); var b = block; for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); } @@ -451,7 +449,7 @@ public class Scheduler { node = data.node; assert node.isCFG(); if (!(node instanceof ReturnNode)) { - for (var out : node._outputs) if (out!=null && out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); + for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); } for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); } @@ -505,7 +503,7 @@ public class Scheduler { */ private static Node findSingleCFGOut(Node node) { if (node instanceof StartNode) { - for(var n:node._outputs) if (n instanceof CProjNode p && p._idx==0) return p; + for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; return null; } assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; @@ -572,7 +570,7 @@ public class Scheduler { switch(block.exit) { case null: break; case IfNode i: - for(var out:i._outputs) if (out instanceof CProjNode p) block.next[p._idx] = blocks.get(p); + for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); break; case RegionNode r: block.next[0] = blocks.get(r); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter12/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index b8437b50..14baadba 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -3,9 +3,7 @@ package com.seaofnodes.simple.fuzzer; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.ScopeNode; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Random; +import java.util.*; import java.util.function.Predicate; /** @@ -70,11 +68,10 @@ public class ScriptGenerator { private static class TypeStruct extends Type { record Field (String name, Type type, TypeStruct struct) {} - final Field[] fields; + Field[] fields; final TypeNullable nullable = new TypeNullable(this); - TypeStruct(String name, Field[] fields) { + TypeStruct(String name) { super(name); - this.fields = fields; } boolean isa(Type other) { return this == other || other == nullable; } } @@ -90,21 +87,7 @@ public class ScriptGenerator { } - private static final Type TYPE_FLT = new Type("flt"); - - private static class TypeInt extends Type { - - TypeInt() { - super("int"); - } - - @Override - boolean isa(Type other) { - return super.isa(other) || other == TYPE_FLT; - } - } - - private static final Type TYPE_INT = new TypeInt(); + private static final Type TYPE_INT = new Type("int"); private static class Variable { @@ -158,6 +141,10 @@ public class ScriptGenerator { * All defined structs. */ private final ArrayList structs = new ArrayList<>(); + /** + * All forward declared structs + */ + private final ArrayList forwardStructs = new ArrayList<>(); /** * Allow to declare structs. */ @@ -301,6 +288,7 @@ public class ScriptGenerator { for (var s:structs) { if (s.name.equals(name)) return s; } + for (var s:forwardStructs) if (s.name.equals(name)) return s; return null; } @@ -333,12 +321,18 @@ public class ScriptGenerator { return v; } + private T randFromList(List list) { + return list.get(random.nextInt(list.size())); + } + /** * Get a random structure name * @return A random structure name */ private String getStructName() { - if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name; + if (!structs.isEmpty() && generateInvalid()) return randFromList(structs).name; + if (!forwardStructs.isEmpty() && generateInvalid()) return randFromList(forwardStructs).name; + // Generate a new random struct name StringBuilder sb = getRandomName(); var v = sb.toString(); @@ -357,16 +351,26 @@ public class ScriptGenerator { * Get a random type * @return A random type */ - private Type getType() { + private Type getType(boolean allowForward) { int t = random.nextInt(10); if( t<5 || structs.isEmpty() ) return TYPE_INT; - if( t<7 ) return TYPE_FLT; - var idx = random.nextInt(structs.size()); - TypeStruct struct = structs.get(idx); + if (allowForward && random.nextInt(10)==0) { + int i=random.nextInt(forwardStructs.size()+1); + if (i>0) return forwardStructs.get(i-1).nullable; + var name = getStructName(); + var struct = new TypeStruct(name); + forwardStructs.add(struct); + return struct.nullable; + } + TypeStruct struct = randFromList(structs); if (random.nextBoolean()) return struct.nullable; return struct; } + private Type getType() { + return getType(false); + } + /** * Generates a program and writes it into the string builder supplied in the constructor. * @return If the program is allowed to be invalid returns if something potentially invalid was generated. @@ -426,19 +430,28 @@ public class ScriptGenerator { * @return 0 */ public int genStruct() { - var name = getStructName(); + int idx = random.nextInt(forwardStructs.size()+10); + TypeStruct struct; + if (idx < 10) { + var name = getStructName(); + struct = new TypeStruct(name); + forwardStructs.add(struct); + } else { + struct = forwardStructs.get(idx-10); + } var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)]; - var struct = new TypeStruct(name, fields); - sb.append("struct ").append(name).append(" {\n"); + sb.append("struct ").append(struct.name).append(" {\n"); indentation += INDENTATION; for (int i=0; i sb.append("true"); case 1 -> sb.append("false"); default -> sb.append(random.nextInt(1<<(rand-2))); } - } else if (type == TYPE_FLT) { - sb.append(random.nextFloat()); + } else if (type instanceof TypeNullable n) { - if (random.nextBoolean()) { + if ((n.base instanceof TypeStruct s && s.fields == null) || random.nextBoolean()) { sb.append("null"); } else { sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name); diff --git a/chapter13/README.md b/chapter13/README.md index ae1d378b..84660eba 100644 --- a/chapter13/README.md +++ b/chapter13/README.md @@ -1,213 +1,371 @@ -# Chapter 13: References +# Chapter 13: Global Code Motion [Previous: Chapter 12](../chapter12/README.md) | [Next: Chapter 14](../chapter14/README.md) -# Table of Contents +This chapter schedules the graphs built in Chapters 10-12, including +reference fields and lazy memory partitions. It chooses a slot-0 control input +for movable operations and preserves the ordering of potentially aliasing +Loads and Stores. + +Also in this chapter, we'll be presenting several fairly detailed graph +algorithms. You might want to brush up on your graph theory! -1. [References](#references) - - [Forward References](#forward-references) - - [Self-referential references](#self-referential-references) - - [Auto deepen forward ref types](#auto-deepen-forward-ref-types) +# Table of Contents -In this chapter, we extend structs to allow for forward and cyclic references. +1. [High Level Overview](#high-level-overview) +2. [Scheduling Walk Through](#scheduling-walk-through) +3. [Scheduling a Loop](#scheduling-a-loop) +4. [Components of the Global Code Motion Algorithm](#components-of-the-global-code-motion-algorithm) +5. [Identification of Basic Blocks in SoN graph](#identification-of-basic-blocks-in-son-graph) +6. [Handling Infinite Loops](#handling-infinite-loops) +7. [Dominators](#dominators) +8. [Loop depth](#loop-depth) +9. [Early Schedule](#early-schedule) +10. [Late Schedule](#late-schedule) +11. [Inserting Anti Dependencies](#inserting-anti-dependencies) +12. [Video Walk Through](#video-walk-through) You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter13) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter12...linear-chapter13) it to the previous chapter. -## Forward References +The original input source program defines a sequence in which things happen. As +we parse the program into Sea of Nodes representation and perform various +optimizations, this sequence is not fully maintained. The optimized Sea of +Nodes graph is driven by the dependencies between nodes rather than the +sequence of instructions in the original source program. -A forward reference occurs when a type or identifier is used before its full -definition is encountered by the compiler. (incomplete types, mutual -references). When forward-reference types are encountered, the compiler may -initially treat them as placeholders or "shallow" representations. +Our goal in this chapter is to look at how we can recover a schedule for executing instructions from an +optimized Sea of Nodes graph. This schedule needs to preserve the semantics of the original source program, +but is otherwise allowed to reorder the execution of instructions. -```java -struct Person { - String name; // No string type, yet - int age; - FamilyTree? tree; // A family tree, or not -} -``` +Since the scheduling algorithm works across the implicit Basic Blocks in the SoN Graph, +we call this a Global Code Motion algorithm; the scheduler can move instructions across Basic Block boundaries. -[edit: Show the TypeStruct mapping] +The primary reference for this algorithm is the GCM GVN paper [^1]. However, the implementation in Simple has some +differences compared to the version described in the paper. -At the time of the `Person` struct definition, the `FamilyTree` struct is not -yet defined. This is a forward reference. To handle this, we assume the struct -will be defined later. +Since this is a complex topic, we will present a high level summary first and then delve into the details. -[edit: swap to code reference] +## High Level Overview -```java -Type t = TYPES.get(tname); - // Assume a forward-reference type - if( t == null ) { - int old2 = _lexer._position; - String id = _lexer.matchId(); - if( id==null ) { - _lexer._position = old; - return null; - } - TYPES.put(tname,t = TypeStruct.make(tname)); // Assume forward ref - _lexer._position = old2; - } -``` +The Sea of Nodes graph has two virtual graphs within it. -Reference fields start out as null, doing anything with them will result in a compile time error. +* There is a control flow graph, represented by certain node types, such as Start, If, Region, etc. +* There is a data graph, primarily driven by Def-Use edges between nodes that produce or consume values or memory. -```java -struct Person { - String name; - int age; - FamilyTree? tree; -} -Person p = new Person; -p.tree.left = null; // Accessing unknown field 'left' from 'null' -``` +From a scheduling point of view, the control flow graph is fixed, and immovable. +However, the nodes that consume or produce data values/memory have some flexibility in terms of when they are executed. The goal of the +scheduling algorithm is to find the best placement of these nodes so that they are both optimum and correct. -It turns out `p.tree` is `null`, so we can't access the field `left` from it. +At its core, the scheduling algorithm works in two phases: -When we are creating the struct, we set up the initial value to Null: +* Schedule Early - in this phase, we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the + first control block where they are dominated by their inputs. +* Schedule Late - in this phase we work backwards from Stop, scheduling uses before definitions, and move data nodes to a block between the first block calculated above, + and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. + Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. -`newStruct`: +## Scheduling Walk Through -```java -private Node newStruct(TypeStruct obj) { - Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - int alias = _aliases.get(obj._name); - for( Field field : obj._fields ) { - store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); - alias++; - } - return n.unkeep(); -} -``` -Zooming in.... +Before we describe the steps in detail, it is instructive to walk through an example and see the changes that occur to the SoN graph during scheduling. + +To motivate the discussion, we will use this example program. ```java -// parsing = FamilyTree? tree; -for( Field field : obj._fields ) { - // field._type = *FamilyTree? - // REMEMBER: - // @Override public TypeMemPtr makeInit() { return NULLPTR; } - store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); - alias++; -} +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; ``` -In a case of a reference `field._type.makeInit()` will return a `NULLPTR`, later this is what the peephole turns into when loading -the field. `p.tree` ---- +First let's look at the graph before scheduling. -```java -struct N { N next; int i; } -N n = new N; -return n.next; // Value reports as null, despite field typed as not-null -``` +These walkthrough diagrams show the precise `S.f` chain and the final +MemMerge consumed by Return. The unchanged default memory comes from Start; +any redundant bulk Phis have folded away. As in Chapters 10 and 11, the diagrams omit +control-flow context, including Regions and their Phi bindings. Small, +detached True/False projections remain where they gate a Load or Store. +Unlike Chapter 10's schematic source gates, these figures show the actual +scheduling inputs at each stage. Entry-block and Loop bindings are omitted. -As shown above, we explicitly allow not-null fields to be null initialized (as -of Chapter 13). We can't allow null to be stored into a not-null field, but we -can allow a not-null field to be null (initially). This will be corrected in a -later chapter. +![Graph1](./docs/graph1.svg) +Observe that -### Self-referential references -Self-cyclic or self-referential types are types that include a reference to themselves as part of their definition. +* Control nodes are immovable. Most are omitted here. +* New nodes already have control inputs, and Phis are bound to Regions; + these bindings remain fixed even though they are not drawn. +* The load `.f` and store `.f=` nodes are still "floating": they have no + control input at this stage, so scheduling must choose their blocks. -Consider the following code: -```java -struct N { N next; int i; } -N n = new N; -n.next = null; // <<-- Compile error, cannot store null into not-null field -return n.next; -``` -The field `next` is of type `N`, which is the same type as the structure `N` itself. -This creates a circular reference. To resolve this, we auto deepen the forward ref type. +Now, let's look at the graph after we run the early schedule. +![Graph2](./docs/graph2.svg) -#### Auto deepen forward ref types -Auto deepening refers to the process where the compiler expands the forward reference into its complete definition. -Here we have a simple linked list implementation, with the payload being a `int`. +The early schedule puts the load `.f` and stores `.f=` in the first basic +block, since their inputs are available there. These entry-block control +bindings are omitted, so the memory view looks unchanged. -```java -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -LLI? next = head.next; -if( next==null ) return 1; -return next.i; -``` +The graph below focuses on memory and data dependencies after late scheduling. +The control-flow scaffold, including the Region, is omitted. Store #22's +control input remains visible as the red edge to the standalone True +projection #8; the Store-to-Load anti-dependency and memory Phis are retained. + +![Graph3](./docs/graph3.svg) + +The snip below shows the main changes in the graph: + +![Store ordered after the earlier load](./docs/graph3-snip.svg) -When `a.next` is encountered, we auto deepen the type so its fields are going to be set. +* The store `.f=` now has an anti-dependency on the load `.f`; this ensures that the store is scheduled after the load, as required by program semantics. We discuss anti-dependencies in detail later. +* Observe also that the store `.f=` is now bound to the True branch of the If node. -This is needed to resolve the stale type that the loop brings around. +## Scheduling a Loop + +We show another example, this time involving a loop. ```java -if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null ) { - e = tmp.make_from((TypeStruct) TYPES.get(tmp._obj._name)); +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; } +return v; ``` +The SoN graph prior to scheduling looks like this: ---- +![Graph4](./docs/graph4.svg) -Normal field syntax works: -e.g. `person.tree.father = new Person;` or -`person.tree.father.name = "Dad";`. +Following early schedule generation, we get: -Reference fields all start out null, and can be tested as such. However, a -non-null reference field can only be stored with a non-null ref. +![Graph5](./docs/graph5.svg) -```java -struct N { N next; int i; } -N n = new N; -return n.next.i; // Value reports as null, despite field typed as not-null -``` +The early schedule places Store #31 at the loop header (its control binding +is omitted). Late scheduling moves it to the True branch #15 of +`if (arg == 5)`, shown by the red control edge: -And: +![Graph6](./docs/graph6.svg) -```java -struct N { N next; int i; } -N n = new N; -n.next = null; // Error, cannot store null into not-null field -return n.next; -``` +The final execution schedule shows that the expression `i/3` can be performed outside the loop because it only depends on the +original value of `arg`, and hence is loop invariant. -Forward references must be specified later and field access is later checked. +## Components of the Global Code Motion Algorithm +We have already alluded to several components of the GCM algorithm in passing above. Here we list them out as well as others we did not mention: +* The SoN graph has implicit basic blocks in the control flow nodes. For the GCM algo, we need to recognize these nodes more explicitly. +* When a program has an infinite loop, it poses a problem for the algo, as nodes can be unreachable from the Stop node. To work around this issue, we need to discover + infinite loops and create a dummy edge connecting the loop to the Stop node. +* Loops are already identified in the Simple SoN graph, so we do not need a loop discovery step. However, we need to compute the loop depth associated with each CFG node. +* In [Chapter 6](../chapter06/README.md) we explained the concept of Dominators. Dominators are key to the GCM algo, and we extend our incremental dominator discovery algo to + ensure that it meets the requirements of the algo. +* We already mentioned the two phases of the algo - the Early Scheduling and the Late Scheduling. +* In addition, we need to add anti-dependency edges between Loads and Stores in certain scenarios to enforce correct execution order. +* There are a few changes to our Node hierarchy to help us implement the GCM algo more conveniently. These changes do not conceptually alter the Node hierarchy we inherited from the previous chapters. -## `LoadNode::idealize` -LoadNode first selects its alias through any MemMerge, then returns `st.val()` -when it finds a matching Store to the same object. -```java - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - ptr() == st.ptr() && _alias == st._alias ) { // Same object and field - assert _name.equals(st._name); // Equiv class aliasing is perfect - return st.val(); // // <<-- {ConstantNode@1651} "null" - } -``` -After alias selection, the Load's `mem()` refers to that initializing Store. -In the next iteration, we will end up with: +## Identification of Basic Blocks in SoN graph + +The Sea of Nodes graph already captures the program's control flow graph. This information is implicit in the control nodes and edges from control nodes to other types of nodes. + +To recap, Control starts at the Start node, via a projection that is bound to the name `$ctrl`. As the control flows in the program, this name binding gets updated, and control is +passed around, until it reaches the Stop node. + +The Basic Block structure of the CFG can be easily constructed by recognizing that certain control nodes start a Basic Block, whereas certain others end a BB. + +Here is the list of all control nodes: + +| Node | Starts a BB | Ends a BB | +|--------|-----------------------------------------|-----------| +| Start | Yes but only via the `$ctrl` projection | No | +| CProj | Yes if input control is an If node | No | +| Region | Yes | No | +| If | No | Yes | +| Return | No | Yes | +| Stop | Yes | Yes | + +Changes to Node hierarchy in this chapter: + +* All control nodes above now extend a base class CFG Node. This allows us to place common functionality of control flow nodes in the base class. +* The CProj node extends the regular Proj node and is used in following cases: + * The `$ctrl` projection off Start + * The True and False projections off If. +* The Never node is a special sub class of If that is used to handle infinite loops as explained later. +* The XCtrl node represents a dead control. + +## Handling Infinite Loops + +Here is an example of code that contains an infinite loop: ```java -... -// expr {ConstantNode@1651} "null" -// ptr = expr._type -// ptr = TypeMemPtr@1510 "null" -// ptr._obj._name = $TOP -TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name); -int idx = base==null ? -1 : base.find(name); // <<-- null -if( idx == -1 ) { - throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); // throws here!!! -} +while (1) {} +return 0; ``` + +First let's look at the graph resulting from this: + +![Graph7](./docs/graph7.svg) + +Now, look at the modified graph after we insert an edge from the infinite loop to Stop node. + +![Graph8](./docs/graph8.svg) + +The implementation is in +[`LoopNode.forceExit()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L57). +It walks the backedge's dominator chain to look for an exit. If none exists, +it inserts a Never node and a never-taken return path, making the loop +reachable to the scheduler's backward walk. + +## Dominators + +In Simple, we compute Dominators incrementally. Our approach relies on the fact that we may delete parts of the graph during peepholes, +but we never introduce new control structure via peepholes. This allows us to use a simple approach described below. + +The CFG node is the base class for all control nodes. It maintains a conservative approximation of dominator depth via `_idepth`. This field is a cached value representing +the immediate dominator depth. Its initial value is `0`, which signifies that it has not yet been computed. The depth is computed on request. + +See [`CFGNode.idepth()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L51) +for the default computation and cache. The overrides in +[`RegionNode`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L94), +[`LoopNode`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L31), +[`StartNode`](src/main/java/com/seaofnodes/simple/node/StartNode.java#L36), and +[`StopNode`](src/main/java/com/seaofnodes/simple/node/StopNode.java#L58) +handle merges, loop entries, and the graph's endpoints. + +If portions of the control flow graph are deleted, then there will be gaps in the `_idepth`, but it still correctly reflects the +invariant that the value of `_idepth` increases as we go down the dominator tree. + +Alongside the dominator depth, which is cached on first compute, a method is provided to get the immediate Dominator node. This +value is not cached as it is only valid at a point in time, and is invalidated as the graph changes. + +[`CFGNode.idom()` and `domLCA()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L60) +provide the default immediate dominator and the walk that finds the least +common ancestor of two dominators. +[`RegionNode.idom()`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L103) +combines the incoming paths; [`LoopNode.idom()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L33) +uses the loop entry. Start and Stop have no immediate dominator. + +## Loop Depth + +Simple's Sea of Nodes graph identifies loops explicitly via Loop nodes. Since the language provides a single way to create a loop, using the `while` statement, +it is not necessary to implement a generic loop discovery process. + +We do however need to compute a loop depth. This is done similarly to how we compute the dominator depth. + +[`CFGNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/CFGNode.java#L78) +inherits depth from its control input, while +[`RegionNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/RegionNode.java#L116) +uses an incoming path. +[`LoopNode.loopDepth()`](src/main/java/com/seaofnodes/simple/node/LoopNode.java#L35) +adds a nesting level and walks the backedge's dominator chain to mark loop exits. +[`StartNode`](src/main/java/com/seaofnodes/simple/node/StartNode.java#L41) and +[`StopNode`](src/main/java/com/seaofnodes/simple/node/StopNode.java#L68) +establish the outermost depth of 1. + +## Early Schedule + +The GCM algorithm proper starts with the computation of the early schedule, during which we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the +first control block where they are dominated by their inputs. + +A pre-condition of this is to ensure that infinite loops have been fixed as described earlier. + +The implementation starts in +[`GlobalCodeMotion.schedEarly()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L62). +[`_rpo_cfg()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L85) +builds the CFG traversal order, and +[`_schedEarly()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L94) +recursively schedules data inputs. It skips recursion through Phi inputs to +avoid entering a data cycle before its control has been scheduled. + +Existing control and Phi/Proj bindings are preserved. Floating nodes receive +the deepest input block as their earliest placement. MemMerge follows this +same rule, ignoring absent entries in its sparse alias table. + +## Late Schedule + +Late scheduling starts at Stop and uses a worklist to place uses before their +definitions. CFG nodes, Phis, projections, and allocations provide fixed +placements. Other nodes wait until their uses have been scheduled; a Load also +waits for memory users that can overwrite or merge its alias. The chosen block +lies between the early placement and the common dominator of all uses, favoring +shallower loops and then deeper control flow. + +The implementation is divided into these steps: + +* [`schedLate()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L114) + manages the placement tables and installs the final control inputs; + [`breadth()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L129) + drives the worklist and wakes definitions and waiting loads as uses finish. +* [`_doSchedLate()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L175) + finds the common dominator of uses, accounts for load anti-dependencies, and + walks toward the early placement. It calls + [`better()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L215) + to compare candidate blocks. +* [`use_block()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L202) + handles Phi uses at their matching incoming control paths, rather than at + the merge itself. + +## Inserting Anti Dependencies + +To ensure that Loads and Stores to the same memory location are correctly +ordered, we insert an edge from the Store to the Load as described below: the +Store must wait for the earlier Load. We call these edges anti-dependencies +because they do not represent the Def-Use dependency that we normally capture +in SoN, and are purely present as scheduling constraints. + +We compute anti-dependencies DURING running schedule late. This is because we rely on the early-schedule, and the late-schedule of the Load's uses (before scheduling the Load). + +Looking backwards from the Load, we follow its memory input to Start's memory +projection, a memory Phi, or a Store. Optimization has already selected the +Load's precise slice through any MemMerge. We inspect users of that memory +definition, keeping only Stores and memory Phis that cover the Load's alias. +Start's single memory projection may supply many aliases, so sharing a memory +input alone does not imply an anti-dependency. + +MemMerge only packages slices; it does not overwrite any of them. Treating it +as a write could make a Load wait for an aggregate which itself depends on the +Load's result. The same alias filter is used both when deciding whether a Load +is ready to schedule and when computing its anti-dependencies. A completed +memory user wakes waiting loads even if their shared memory definition has +already been scheduled. + +Since we're in the middle of `schedLate`, we have already computed all the late +schedules of a Load's users, and we have the Loads *Least Common Ancestor* of +uses, the LCA or late position. We inspect the set of mem-defs that might +impact the Load, and either add an anti-dependency from Store to Load, or raise +the Loads effective LCA. For Phi mem-defs, we look at the Phi inputs and place +an effective use on that block; this will be used to raise the Load's LCA. For +stores, we do the same - until/unless we find stores with the SAME block as the +Load's LCA. Then we add the anti-dependency edge to force ordering within the +same block. + +See [`antiUse()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L224) +for the alias filter, and +[`find_anti_dep()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L233) +for marking the Load's candidate blocks and inspecting competing memory users. +[`anti_dep()`](src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java#L260) +walks the competing operation's dominator interval, raises the Load's LCA, +and adds a Store-to-Load edge when required. It checks the Store's full interval +because the evaluator's scheduler may place that Store independently of GCM. + +## Video Walk Through + +Please watch the following video for a detailed walk through of the implementation. + +* [Coffee Compiler Club - 21 June 2024](https://youtu.be/5Po0gxfE7LA?feature=shared). +* [Coffee Compiler Club - 26 June 2024](https://www.youtube.com/watch?v=HnN2YzmFb-8&t=0s) + +[^1]: Cliff Click. (1995). + Global Code Motion Global Value Numbering. + +Next, [Chapter 14](../chapter14/README.md) adds floating-point arithmetic and +narrow numeric types to this scheduled compiler. diff --git a/chapter13/chapter13.iml b/chapter13/chapter13.iml new file mode 100644 index 00000000..6fa60f3f --- /dev/null +++ b/chapter13/chapter13.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chapter13/docs/13-grammar.md b/chapter13/docs/13-grammar.md index 6cbbf388..4e7ae5b4 100644 --- a/chapter13/docs/13-grammar.md +++ b/chapter13/docs/13-grammar.md @@ -11,22 +11,21 @@ statement : returnStatement | structDeclaration | declStatement - | blockStatment + | blockStatement | expressionStatement | ifStatement | whileStatement | breakStatement - | continueStatment + | continueStatement ; PRIMTYPE : 'int' - | 'flt' ; field : PRIMTYPE IDENTIFIER ';' - | structName IDENTIFIER ('?')? ';' + | structName ('?')? IDENTIFIER ';' ; fields @@ -103,7 +102,7 @@ newExpression ; fieldExpression - : primaryExpresson '.' IDENTIFIER + : primaryExpression '.' IDENTIFIER ; primaryExpression @@ -123,7 +122,7 @@ INTEGER_LITERAL ; IDENTIFIER - : NON_DIGIT (NON_DIGIT | DIGIT)* + : NON_DIGIT (NON_DIGIT | DEC_DIGIT)* ; NON_DIGIT: [a-zA-Z_]; diff --git a/chapter13/docs/Makefile b/chapter13/docs/Makefile index 12659b8f..6fa0f057 100644 --- a/chapter13/docs/Makefile +++ b/chapter13/docs/Makefile @@ -1,12 +1 @@ -GV := $(wildcard *.gv) -SVG := $(patsubst %.gv,%.svg,$(GV)) - -.PHONY: all -all: $(SVG) - -%.svg: %.gv - dot -Tsvg $< > $@ - -.PHONY: clean -clean: - @rm -f $(SVG) +include ../../graph/docs.mk diff --git a/chapter13/docs/graph1.gv b/chapter13/docs/graph1.gv new file mode 100644 index 00000000..4d6ee1fe --- /dev/null +++ b/chapter13/docs/graph1.gv @@ -0,0 +1,79 @@ +digraph chapter13 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p1[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p2[taillabel=1]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; +} diff --git a/chapter13/docs/graph1.json b/chapter13/docs/graph1.json new file mode 100644 index 00000000..aac69ce2 --- /dev/null +++ b/chapter13/docs/graph1.json @@ -0,0 +1,25 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph1.svg", + "step": 1, + "roots": [25], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, + {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, + {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph1.svg b/chapter13/docs/graph1.svg new file mode 100644 index 00000000..93a2a03f --- /dev/null +++ b/chapter13/docs/graph1.svg @@ -0,0 +1,123 @@ +#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — DATA +[1] → #6 MEM +[2] → #224 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMnew S  #9#9 new S +Type not computed + +[0] → — CTRL0#2  #11#11 #2 +Type not computed + +.f =  #12#12 .f = +Type not computed + +[0] → — DATA +[1] → #6 MEM +[2] → #9 DATA +[3] → #11 DATA0123new S  #13#13 new S +Type not computed + +[0] → — CTRL0.f =  #14#14 .f = +Type not computed + +[0] → — DATA +[1] → #12 MEM +[2] → #13 DATA +[3] → #4 DATA0123.f  #16#16 .f +Type not computed + +[0] → — DATA +[1] → #14 MEM +[2] → #9 DATA012#1  #21#21 #1 +Type not computed + +.f =  #22#22 .f = +Type not computed + +[0] → — DATA +[1] → #14 MEM +[2] → #9 DATA +[3] → #21 DATA0123Return  #25#25 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #22 MEM +[2] → #14 MEM012 diff --git a/chapter13/docs/graph2.gv b/chapter13/docs/graph2.gv new file mode 100644 index 00000000..94d94dae --- /dev/null +++ b/chapter13/docs/graph2.gv @@ -0,0 +1,84 @@ +digraph chapter13 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p0[taillabel=0 color=red]; + Store12 -> Start2:p1[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Start2:p0[taillabel=0 color=red]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Start2:p0[taillabel=0 color=red]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p2[taillabel=1]; + Store22 -> Start2:p0[taillabel=0 color=red]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; + MemMerge -> Region23[taillabel=0 color=green]; +} diff --git a/chapter13/docs/graph2.json b/chapter13/docs/graph2.json new file mode 100644 index 00000000..47689c72 --- /dev/null +++ b/chapter13/docs/graph2.json @@ -0,0 +1,25 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph2.svg", + "step": 1, + "roots": [25], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, + {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, + {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph2.svg b/chapter13/docs/graph2.svg new file mode 100644 index 00000000..05210749 --- /dev/null +++ b/chapter13/docs/graph2.svg @@ -0,0 +1,123 @@ +#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #224 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMnew S  #9#9 new S +Type not computed + +[0] → — CTRL0#2  #11#11 #2 +Type not computed + +.f =  #12#12 .f = +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #9 DATA +[3] → #11 DATA0123new S  #13#13 new S +Type not computed + +[0] → — CTRL0.f =  #14#14 .f = +Type not computed + +[0] → — CTRL +[1] → #12 MEM +[2] → #13 DATA +[3] → #4 DATA0123.f  #16#16 .f +Type not computed + +[0] → — CTRL +[1] → #14 MEM +[2] → #9 DATA012#1  #21#21 #1 +Type not computed + +.f =  #22#22 .f = +Type not computed + +[0] → — CTRL +[1] → #14 MEM +[2] → #9 DATA +[3] → #21 DATA0123Return  #25#25 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #22 MEM +[2] → #14 MEM012 diff --git a/chapter13/docs/graph3-snip.jpg b/chapter13/docs/graph3-snip.jpg new file mode 100644 index 00000000..804a32c4 Binary files /dev/null and b/chapter13/docs/graph3-snip.jpg differ diff --git a/chapter13/docs/graph3-snip.json b/chapter13/docs/graph3-snip.json new file mode 100644 index 00000000..548dbdf3 --- /dev/null +++ b/chapter13/docs/graph3-snip.json @@ -0,0 +1,17 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph3-snip.svg", + "step": 1, + "roots": [22], + "scope": 0, + "nodes": [ + {"id": 8, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 0, "role": "MEM", "label": null}, {"idx": 2, "def": 0, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null}, + {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}, {"idx": 4, "def": 16, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph3-snip.svg b/chapter13/docs/graph3-snip.svg new file mode 100644 index 00000000..9f5218af --- /dev/null +++ b/chapter13/docs/graph3-snip.svg @@ -0,0 +1,91 @@ +#16[1] → #14 · MEM#16[2] → #9 · DATA#22[0] → #8 · CTRL#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#22[4] → #16 · DATATrue  #8/0#8 True +Projection 0 of #0 +Type not computed + +new S  #9#9 new S +Type not computed + +[0] → — CTRL0.f =  #14#14 .f = +Type not computed + +[0] → — CTRL +[1] → — MEM +[2] → — DATA +[3] → — DATA0123.f  #16#16 .f +Type not computed + +[0] → — CTRL +[1] → #14 MEM +[2] → #9 DATA012#1  #21#21 #1 +Type not computed + +.f =  #22#22 .f = +Type not computed + +[0] → #8 CTRL +[1] → #14 MEM +[2] → #9 DATA +[3] → #21 DATA +[4] → #16 DATA01234 diff --git a/chapter13/docs/graph3.gv b/chapter13/docs/graph3.gv new file mode 100644 index 00000000..53c9aafe --- /dev/null +++ b/chapter13/docs/graph3.gv @@ -0,0 +1,85 @@ +digraph chapter13 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p0[taillabel=0 color=red]; + Store12 -> Start2:p1[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Start2:p0[taillabel=0 color=red]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Start2:p0[taillabel=0 color=red]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p2[taillabel=1]; + Store22 -> If17:p0[taillabel=0 color=red]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Store22 -> Load16[taillabel=4]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; + MemMerge -> Region23[taillabel=0 color=green]; +} diff --git a/chapter13/docs/graph3.json b/chapter13/docs/graph3.json new file mode 100644 index 00000000..e44eb1b1 --- /dev/null +++ b/chapter13/docs/graph3.json @@ -0,0 +1,26 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph3.svg", + "step": 1, + "roots": [25], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 224, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 8, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 11, "label": "#2", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 12, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null}, + {"id": 13, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 14, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 13, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 16, "label": ".f", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "#1", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 22, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}, {"idx": 4, "def": 16, "role": "DATA", "label": null}], "proj": null}, + {"id": 25, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null}, + {"id": 224, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph3.svg b/chapter13/docs/graph3.svg new file mode 100644 index 00000000..0247b5c6 --- /dev/null +++ b/chapter13/docs/graph3.svg @@ -0,0 +1,128 @@ +#1[1] → #6 · MEM#1[2] → #224 · MEM#12[1] → #6 · MEM#12[2] → #9 · DATA#12[3] → #11 · DATA#14[1] → #12 · MEM#14[2] → #13 · DATA#14[3] → #4 · DATA#16[1] → #14 · MEM#16[2] → #9 · DATA#22[0] → #8 · CTRL#22[1] → #14 · MEM#22[2] → #9 · DATA#22[3] → #21 · DATA#22[4] → #16 · DATA#25[1] → #1 · MEM#25[2] → #16 · DATA#224[1] → #22 · MEM#224[2] → #14 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #224 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMTrue  #8/0#8 True +Projection 0 of #0 +Type not computed + +new S  #9#9 new S +Type not computed + +[0] → — CTRL0#2  #11#11 #2 +Type not computed + +.f =  #12#12 .f = +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #9 DATA +[3] → #11 DATA0123new S  #13#13 new S +Type not computed + +[0] → — CTRL0.f =  #14#14 .f = +Type not computed + +[0] → — CTRL +[1] → #12 MEM +[2] → #13 DATA +[3] → #4 DATA0123.f  #16#16 .f +Type not computed + +[0] → — CTRL +[1] → #14 MEM +[2] → #9 DATA012#1  #21#21 #1 +Type not computed + +.f =  #22#22 .f = +Type not computed + +[0] → #8 CTRL +[1] → #14 MEM +[2] → #9 DATA +[3] → #21 DATA +[4] → #16 DATA01234Return  #25#25 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #16 DATA012MemPhi_2  #224#224 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #22 MEM +[2] → #14 MEM012 diff --git a/chapter13/docs/graph4.gv b/chapter13/docs/graph4.gv new file mode 100644 index 00000000..d7082072 --- /dev/null +++ b/chapter13/docs/graph4.gv @@ -0,0 +1,110 @@ +digraph chapter13 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p1[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p2[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2:p2[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; +} diff --git a/chapter13/docs/graph4.json b/chapter13/docs/graph4.json new file mode 100644 index 00000000..add08b6b --- /dev/null +++ b/chapter13/docs/graph4.json @@ -0,0 +1,24 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph4.svg", + "step": 1, + "roots": [37], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, + {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, + {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph4.svg b/chapter13/docs/graph4.svg new file mode 100644 index 00000000..9ca92879 --- /dev/null +++ b/chapter13/docs/graph4.svg @@ -0,0 +1,120 @@ +#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — DATA +[1] → #6 MEM +[2] → #230 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMarg  #7/2#7 arg +Projection 2 of #2 +Type not computed + +[0] → #2 DATAnew S  #9#9 new S +Type not computed + +[0] → — CTRL0.f =  #10#10 .f = +Type not computed + +[0] → — DATA +[1] → #6 MEM +[2] → #9 DATA +[3] → #4 DATA0123//  #21#21 // +Type not computed + +[0] → — DATA +[1] → #7 DATA +[2] → #22 DATA012#3  #22#22 #3 +Type not computed + +.f =  #31#31 .f = +Type not computed + +[0] → — DATA +[1] → #230 MEM +[2] → #9 DATA +[3] → #21 DATA0123Return  #37#37 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #10 MEM +[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #31 MEM +[2] → #230 MEM012 diff --git a/chapter13/docs/graph5.gv b/chapter13/docs/graph5.gv new file mode 100644 index 00000000..8189f6dd --- /dev/null +++ b/chapter13/docs/graph5.gv @@ -0,0 +1,117 @@ +digraph chapter13 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p0[taillabel=0 color=red]; + Store10 -> Start2:p1[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p2[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Loop11[taillabel=0 color=red]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2[taillabel=0 color=red]; + Div21 -> Start2:p2[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> Loop11[taillabel=0 color=red]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> Loop11[taillabel=0 color=red]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Loop11[taillabel=0 color=red]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; + MemMerge -> Loop11[taillabel=0 color=green]; +} diff --git a/chapter13/docs/graph5.json b/chapter13/docs/graph5.json new file mode 100644 index 00000000..6b1722f6 --- /dev/null +++ b/chapter13/docs/graph5.json @@ -0,0 +1,24 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph5.svg", + "step": 1, + "roots": [37], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, + {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, + {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph5.svg b/chapter13/docs/graph5.svg new file mode 100644 index 00000000..c120550f --- /dev/null +++ b/chapter13/docs/graph5.svg @@ -0,0 +1,120 @@ +#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #230 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMarg  #7/2#7 arg +Projection 2 of #2 +Type not computed + +[0] → #2 DATAnew S  #9#9 new S +Type not computed + +[0] → — CTRL0.f =  #10#10 .f = +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #9 DATA +[3] → #4 DATA0123//  #21#21 // +Type not computed + +[0] → — CTRL +[1] → #7 DATA +[2] → #22 DATA012#3  #22#22 #3 +Type not computed + +.f =  #31#31 .f = +Type not computed + +[0] → — CTRL +[1] → #230 MEM +[2] → #9 DATA +[3] → #21 DATA0123Return  #37#37 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #10 MEM +[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #31 MEM +[2] → #230 MEM012 diff --git a/chapter13/docs/graph6.gv b/chapter13/docs/graph6.gv new file mode 100644 index 00000000..47165fdb --- /dev/null +++ b/chapter13/docs/graph6.gv @@ -0,0 +1,117 @@ +digraph chapter13 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl$memarg
+
> + ]; + MemMerge [ shape=box label="MemMerge" ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p0[taillabel=0 color=red]; + Store10 -> Start2:p1[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p2[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Loop11[taillabel=0 color=red]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2:p0[taillabel=0 color=red]; + Div21 -> Start2:p2[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> If16:p0[taillabel=0 color=red]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> If25:p0[taillabel=0 color=red]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Region32[taillabel=0 color=red]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; + MemMerge -> If16:p1[taillabel=0 color=green]; +} diff --git a/chapter13/docs/graph6.json b/chapter13/docs/graph6.json new file mode 100644 index 00000000..c63c717f --- /dev/null +++ b/chapter13/docs/graph6.json @@ -0,0 +1,25 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph6.svg", + "step": 1, + "roots": [37], + "scope": 0, + "nodes": [ + {"id": 1, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 6, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 2, "role": "MEM", "label": null}], "proj": {"par": 2, "idx": 1}}, + {"id": 7, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": {"par": 2, "idx": 2}}, + {"id": 9, "label": "new S", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}], "proj": null}, + {"id": 10, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 6, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 15, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}}, + {"id": 21, "label": "//", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}, {"idx": 2, "def": 22, "role": "DATA", "label": null}], "proj": null}, + {"id": 22, "label": "#3", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 31, "label": ".f =", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 230, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}, {"idx": 3, "def": 21, "role": "DATA", "label": null}], "proj": null}, + {"id": 37, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 1, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}, + {"id": 230, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 233, "role": "MEM", "label": null}], "proj": null}, + {"id": 233, "label": "MemPhi_2", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 31, "role": "MEM", "label": null}, {"idx": 2, "def": 230, "role": "MEM", "label": null}], "proj": null} + ], + "groups": [ + ] +} diff --git a/chapter13/docs/graph6.svg b/chapter13/docs/graph6.svg new file mode 100644 index 00000000..d6dec88f --- /dev/null +++ b/chapter13/docs/graph6.svg @@ -0,0 +1,124 @@ +#1[1] → #6 · MEM#1[2] → #230 · MEM#10[1] → #6 · MEM#10[2] → #9 · DATA#10[3] → #4 · DATA#21[1] → #7 · DATA#21[2] → #22 · DATA#31[0] → #15 · CTRL#31[1] → #230 · MEM#31[2] → #9 · DATA#31[3] → #21 · DATA#37[1] → #1 · MEM#37[2] → #9 · DATA#230[1] → #10 · MEM#230[2] → #233 · MEM#233[1] → #31 · MEM#233[2] → #230 · MEMMemMerge  #1#1 MemMerge +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #230 MEM012Start  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +$mem  #6/1#6 $mem +Projection 1 of #2 +Type not computed + +[0] → #2 MEMarg  #7/2#7 arg +Projection 2 of #2 +Type not computed + +[0] → #2 DATAnew S  #9#9 new S +Type not computed + +[0] → — CTRL0.f =  #10#10 .f = +Type not computed + +[0] → — CTRL +[1] → #6 MEM +[2] → #9 DATA +[3] → #4 DATA0123True  #15/0#15 True +Projection 0 of #0 +Type not computed + +//  #21#21 // +Type not computed + +[0] → — CTRL +[1] → #7 DATA +[2] → #22 DATA012#3  #22#22 #3 +Type not computed + +.f =  #31#31 .f = +Type not computed + +[0] → #15 CTRL +[1] → #230 MEM +[2] → #9 DATA +[3] → #21 DATA0123Return  #37#37 Return +Type not computed + +[0] → — CTRL +[1] → #1 MEM +[2] → #9 DATA012MemPhi_2  #230#230 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #10 MEM +[2] → #233 MEM012MemPhi_2  #233#233 MemPhi_2 +Type not computed + +[0] → — ASSOC +[1] → #31 MEM +[2] → #230 MEM012 diff --git a/chapter13/docs/graph7.gv b/chapter13/docs/graph7.gv new file mode 100644 index 00000000..b6507e53 --- /dev/null +++ b/chapter13/docs/graph7.gv @@ -0,0 +1,23 @@ +digraph chapter13 { + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl
+
> + ]; + Con_4 [ label="#0" ]; + Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + { rank=same; Loop8;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Loop8 -> Start2:p0[taillabel=1 color=red]; + Loop8 -> Loop8[taillabel=2 color=red constraint=false]; +} diff --git a/chapter13/docs/graph7.json b/chapter13/docs/graph7.json new file mode 100644 index 00000000..0e3efb9f --- /dev/null +++ b/chapter13/docs/graph7.json @@ -0,0 +1,16 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph7.svg", + "step": 1, + "roots": [], + "scope": 0, + "nodes": [ + {"id": 1, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": {"par": 2, "idx": 0}}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 8, "label": "Loop", "type": null, "kind": "LOOP", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 1, "role": "CTRL", "label": null}, {"idx": 2, "def": 8, "role": "CTRL", "label": null}], "proj": null} + ], + "groups": [ + {"id": 8, "par": 0, "nodes": [8]} + ] +} diff --git a/chapter13/docs/graph7.svg b/chapter13/docs/graph7.svg new file mode 100644 index 00000000..15d886dc --- /dev/null +++ b/chapter13/docs/graph7.svg @@ -0,0 +1,78 @@ +#8[1] → #1 · CTRL#8[2] → #8 · CTRL$ctrl  #1/0#1 $ctrl +Projection 0 of #2 +Type not computed + +[0] → #2 CTRLStart  #2#2 Start +Type not computed + +#0  #4#4 #0 +Type not computed + +Loop  #8#8 Loop +Type not computed + +[0] → — DATA +[1] → #1 CTRL +[2] → #8 CTRL012Loop  #8Loop · 1 nodes−Fold region diff --git a/chapter13/docs/graph8.gv b/chapter13/docs/graph8.gv new file mode 100644 index 00000000..442b6843 --- /dev/null +++ b/chapter13/docs/graph8.gv @@ -0,0 +1,45 @@ +digraph chapter13 { +/* +while (1) {} +return 0; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Never18 [ shape=plaintext label=< + + + +
Never
+ + +
TrueFalse
+
> + ]; + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrl
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + { rank=same; Loop8;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Never18 -> Loop8[taillabel=0 color=red]; + Never18 -> Con_4[taillabel=1]; + Stop3 -> Return21[taillabel=0 color=red]; + Return21 -> Never18:p0[taillabel=0 color=red]; + Return21 -> Con_4[taillabel=2]; + Loop8 -> Start2:p0[taillabel=1 color=red]; + Loop8 -> Never18:p1[taillabel=2 color=red constraint=false]; +} diff --git a/chapter13/docs/graph8.json b/chapter13/docs/graph8.json new file mode 100644 index 00000000..b7d4f41c --- /dev/null +++ b/chapter13/docs/graph8.json @@ -0,0 +1,21 @@ +{ + "ver": 1, + "comp": "chapter13/docs/graph8.svg", + "step": 1, + "roots": [], + "scope": 0, + "nodes": [ + {"id": 1, "label": "True", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 18, "role": "CTRL", "label": null}], "proj": {"par": 18, "idx": 0}}, + {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null}, + {"id": 3, "label": "Stop", "type": null, "kind": "STOP", "edges": [{"idx": 0, "def": 21, "role": "CTRL", "label": null}], "proj": null}, + {"id": 4, "label": "#0", "type": null, "kind": "DATA", "edges": [], "proj": null}, + {"id": 5, "label": "False", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 18, "role": "CTRL", "label": null}], "proj": {"par": 18, "idx": 1}}, + {"id": 6, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": {"par": 2, "idx": 0}}, + {"id": 8, "label": "Loop", "type": null, "kind": "LOOP", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 6, "role": "CTRL", "label": null}, {"idx": 2, "def": 5, "role": "CTRL", "label": null}], "proj": null}, + {"id": 18, "label": "Never", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 8, "role": "CTRL", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}], "proj": null}, + {"id": 21, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}, {"idx": 1, "def": 0, "role": "DATA", "label": null}, {"idx": 2, "def": 4, "role": "DATA", "label": null}], "proj": null} + ], + "groups": [ + {"id": 8, "par": 0, "nodes": [1, 5, 8, 18]} + ] +} diff --git a/chapter13/docs/graph8.svg b/chapter13/docs/graph8.svg new file mode 100644 index 00000000..d96a9a7d --- /dev/null +++ b/chapter13/docs/graph8.svg @@ -0,0 +1,98 @@ +#3[0] → #21 · CTRL#8[1] → #6 · CTRL#8[2] → #5 · CTRL#18[0] → #8 · CTRL#18[1] → #4 · DATA#21[0] → #1 · CTRL#21[2] → #4 · DATATrue  #1/0#1 True +Projection 0 of #18 +Type not computed + +[0] → #18 CTRLStart  #2#2 Start +Type not computed + +Stop  #3#3 Stop +Type not computed + +[0] → #21 CTRL0#0  #4#4 #0 +Type not computed + +False  #5/1#5 False +Projection 1 of #18 +Type not computed + +[0] → #18 CTRL$ctrl  #6/0#6 $ctrl +Projection 0 of #2 +Type not computed + +[0] → #2 CTRLLoop  #8#8 Loop +Type not computed + +[0] → — DATA +[1] → #6 CTRL +[2] → #5 CTRL012Never  #18#18 Never +Type not computed + +[0] → #8 CTRL +[1] → #4 DATA01Return  #21#21 Return +Type not computed + +[0] → #1 CTRL +[1] → — DATA +[2] → #4 DATA012Loop  #8Loop · 4 nodes−Fold region diff --git a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java index cc668f18..32a85173 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java @@ -68,7 +68,6 @@ public class Parser { add("continue"); add("else"); add("false"); - add("flt"); add("if"); add("int"); add("new"); @@ -98,7 +97,6 @@ public class Parser { IterPeeps.reset(); TYPES.clear(); TYPES.put("int",TypeInteger.BOT); - TYPES.put("flt",TypeFloat .BOT); SCHEDULED = false; _lexer = new Lexer(source); _scope = new ScopeNode(); @@ -437,9 +435,6 @@ public class Parser { throw error("Undefined name '" + name + "'"); t = _scope.lookupDeclaredType(name); } - // Auto-widen int to float - if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) - expr = new ToFloatNode(expr).peephole(); // Auto-deepen forward ref types Type e = expr._type; if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null ) @@ -506,7 +501,7 @@ public class Parser { else break; // Peepholes can fire, but lhs is already "hooked", kept alive lhs.setDef(idx,parseAddition()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); if( negate ) // Extra negate for != lhs = new NotNode(lhs).peephole(); } @@ -529,7 +524,7 @@ public class Parser { else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); } return lhs; } @@ -550,7 +545,7 @@ public class Parser { else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); - lhs = lhs.widen().peephole(); + lhs = lhs.peephole(); } return lhs; } @@ -564,7 +559,7 @@ public class Parser { * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { - if (match("-")) return new MinusNode(parseUnary()).widen().peephole(); + if (match("-")) return new MinusNode(parseUnary()).peephole(); if (match("!")) return new NotNode(parseUnary()).peephole(); return parsePostfix(parsePrimary()); } @@ -663,7 +658,6 @@ public class Parser { * *

      *     integerLiteral: [1-9][0-9]* | [0]
-     *     floatLiteral: [digits].[digits]?[e [digits]]?
      * 
*/ private ConstantNode parseLiteral() { @@ -821,37 +815,19 @@ public class Parser { return String.valueOf(peek()); } - + boolean isNumber() {return isNumber(peek());} boolean isNumber(char ch) {return Character.isDigit(ch);} - // Return a constant Type, either TypeInteger or TypeFloat private Type parseNumber() { - int old = _position; - int len = isLongOrDouble(); - if( len > 0 ) { - if( len > 1 && _input[old]=='0' ) - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(new String(_input,old,len))); - } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); + String snum = parseNumberString(); + if (snum.length() > 1 && snum.charAt(0) == '0') + throw error("Syntax error: integer values cannot start with '0'"); + return TypeInteger.constant(Long.parseLong(snum)); } private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - _position += len; - return new String(_input,old,len); - } - - // Return +len that ends a long - // Return -len that ends a double - private int isLongOrDouble() { - int old = _position; - char c; - while( Character.isDigit(c=nextChar()) ) ; - if( !(c=='e' || c=='.') ) - return --_position - old; - while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; - return -(--_position - old); + int start = _position; + while (isNumber(nextChar())) ; + return new String(_input, start, --_position - start); } // First letter of an identifier diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index fd713156..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class AddFNode extends Node { - public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override protected String format() { return "(%1+%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()+i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Add of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} 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 94fee330..eb7e2793 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -169,7 +169,6 @@ public class AddNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } @Override public String err() { if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; 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 e515a5f1..63452dba 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -2,11 +2,8 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.type.TypeMemPtr; @@ -27,21 +24,19 @@ abstract public class BoolNode extends Node { @Override public Type compute() { - if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) { - if (i0.isConstant() && i1.isConstant()) - return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); - } - if( in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1 ) { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); } return TypeInteger.BOT; } - boolean doOp(long lhs, long rhs) { throw Utils.TODO(); } - boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } + abstract boolean doOp(long lhs, long rhs); @Override public Node idealize() { @@ -53,9 +48,11 @@ abstract public class BoolNode extends Node { if( this instanceof EQ ) { if( !(in(2) instanceof ConstantNode) ) { // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) + if( in(1) instanceof ConstantNode ) + return new EQ(in(2),in(1)); // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); + else if( in(1)._nid > in(2)._nid ) + return new EQ(in(2),in(1)); } // Equals X==0 becomes a !X if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) @@ -71,12 +68,7 @@ abstract public class BoolNode extends Node { return null; } - public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } Node copyF() { return new EQF(null,null); } } - public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } Node copyF() { return new LTF(null,null); }} - public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } Node copyF() { return new LEF(null,null); } } - - public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } Node copyF() { return null; } } - public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } Node copyF() { return null; } } - public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } Node copyF() { return null; } } - + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } + public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index a3f4318d..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class DivFNode extends Node { - public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()/i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Div of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } -} 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 cd52d117..d7244ace 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -37,5 +37,4 @@ public class DivNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 1b45c7c5..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0) - return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; - return TypeFloat.BOT; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusFNode minus ) - return minus.in(1); - - return null; - } -} 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 ae450b22..5b65b3fb 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -27,5 +27,4 @@ public class MinusNode extends Node { return null; } - @Override Node copyF() { return new MinusFNode(null); } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 6a57f3c5..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MulFNode extends Node { - public MulFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "MulF"; } - - @Override protected String format() { return "(%1*%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()*i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); } -} 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 7fd52399..b0359a52 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -51,7 +51,6 @@ public class MulNode extends Node { return null; } @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } - @Override Node copyF() { return new MulFNode(null,null); } @Override public String err() { if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; 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 7448e05e..2c8596fa 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java @@ -8,7 +8,6 @@ import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; import java.util.*; import java.util.function.Function; @@ -497,26 +496,6 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable /** Return block start from a isCFG() */ public Node getBlockStart() { return null; } - // Semantic change to the graph (so NOT a peephole), used by the Parser. - // If any input is a float, flip to a float-flavored opcode and widen any - // non-float input. - public final Node widen() { - if( !hasFloatInput() ) return this; - Node flt = copyF(); - if( flt==null ) return this; - for( int i=1; itop, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot // If input in null then true diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 7c1a5dde..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class SubFNode extends Node { - public SubFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "SubF"; } - - @Override protected String format() { return "(%1-%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()-i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Sub of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } -} 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 f36f2af5..1589abe7 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -57,5 +57,4 @@ public class SubNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java deleted file mode 100644 index 26cf50e1..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; - - -public class ToFloatNode extends Node { - public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } - - @Override protected String format() { return "(flt)%1"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeInteger i0 && i0.isConstant() ) - return TypeFloat.constant(i0.value()); - return TypeFloat.BOT; - } - - @Override public Node idealize() { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); } -} diff --git a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 9d996c03..e65963db 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 13's view of the IR; browser, transport and layout are shared. */ +/** Chapter 12's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter { @Override protected String[] edgeNames(Node n) { diff --git a/chapter13/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter13/src/main/java/com/seaofnodes/simple/type/Type.java index 1f211430..93e27c76 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/type/Type.java @@ -33,12 +33,11 @@ public class Type { static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) static final byte TSIMPLE = 4; // End of the Simple Types static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TFLT = 6; // All Integers; see TypeInteger - static final byte TTUPLE = 7; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 8; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TMEMPTR = 9; // Memory pointer type - static final byte TSTRUCT =10; // Structs; tuples with named fields - static final byte TFLD =11; // Fields into struct + static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias + static final byte TMEMPTR = 8; // Memory pointer type + static final byte TSTRUCT = 9; // Structs; tuples with named fields + static final byte TFLD =10; // Fields into struct public final byte _type; @@ -82,16 +81,14 @@ public class Type { // ---------------------------------------------------------- - // Notes on Type interning: At the moment it is not easy to reset the - // interned types because we hold static references to several types and - // these are scattered around. This means the INTERN cache will retain all - // types from every run of the Parser. For this to work correctly types - // must be rigorous about defining when they are the same. Also types need - // to be immutable once defined. The rationale for interning is - // *correctness* with cyclic type definitions. Simple structural recursive - // checks go exponential with merely sharing, but with cycles they will - // stack overflow and crash. Intering means we do not need to have sharing - // checks with every type compare, only during interning. + // Notes on Type interning. + // At the moment it is not easy to reset the interned types + // because we hold static references to several types and these are scattered + // around. This means the INTERN cache will retain all types from + // every run of the Parser. For this to work correctly types must be + // rigorous about defining when they are the same. Also types need to be + // immutable once defined. + // The rationale for interning is performance. // Factory method which interns "this" public T intern() { diff --git a/chapter13/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter13/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index 738aebc9..00000000 --- a/chapter13/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Float Type - */ -public class TypeFloat extends Type { - - public final static TypeFloat TOP = make(false, 0); - public final static TypeFloat BOT = make(false, 1); - public final static TypeFloat ZERO= make(true , 0); - - public final boolean _is_con; - - /** - * The constant value or - * if not constant then 1=bottom, 0=top. - */ - public final double _con; - - private TypeFloat(boolean is_con, double con) { - super(TFLT); - _is_con = is_con; - _con = con; - } - public static TypeFloat make(boolean is_con, double con) { - return new TypeFloat(is_con,con).intern(); - } - - public static TypeFloat constant(double con) { return make(true, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(_con); - } - - @Override public String str() { - if( this==TOP ) return "~flt"; - if( this==BOT ) return "flt"; - return ""+_con; - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append("Int"); - } - - @Override - public boolean isHighOrConst() { return _is_con || _con==0; } - - @Override - public boolean isConstant() { return _is_con; } - - public double value() { return _con; } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeFloat) - TypeFloat i = (TypeFloat)other; // Contract - // BOT wins - if ( this==BOT ) return this; - if ( i ==BOT ) return i ; - // TOP loses - if ( i ==TOP ) return this; - if ( this==TOP ) return i ; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return BOT; - } - - @Override - public Type dual() { - if( isConstant() ) return this; // Constants are a self-dual - return _con==0 ? BOT : TOP; - } - - @Override - public Type glb() { return BOT; } - - @Override public TypeFloat makeInit() { return ZERO; } - - @Override - int hash() { return (int)(Double.hashCode(_con) ^ (_is_con ? 0 : 0x4000)); } - @Override - public boolean eq( Type t ) { - TypeFloat i = (TypeFloat)t; // Contract - return _con==i._con && _is_con==i._is_con; - } -} diff --git a/chapter13/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter13/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 2599a383..02de7cc8 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -8,14 +8,12 @@ import java.util.ArrayList; */ public class TypeStruct extends Type { - // A Struct has a name and a set of fields; the fields themselves have - // names and types. Briefly during parsing its allowed to have a - // forward-ref to a Struct; in this case the _fields array is null. - // Its illegal to attempt to load a field from a forward-ref struct. - // - // During the normal optimization run, struct types "bottom out" at further - // struct references, so we don't have to handle e.g. cyclic types. The - // "bottom out" is again the forward-ref struct. + // Immutable shallow references, resolved by name in Parser.TYPES when needed. + // A name-only struct has _fields == null, even after its definition is known. + // For LLI { LLI? next; int i; }, L0 is name-only and L1.next points to L0; + // TYPES["LLI"] holds L1. We deliberately stop recursive type expansion here, + // keeping interning and lattice operations finite and acyclic. Never patch + // L0 into L1: field access looks up the complete declaration by name instead. public final String _name; public final Field[] _fields; @@ -40,7 +38,7 @@ public class TypeStruct extends Type { private static final TypeStruct S1F = make("S1"); private static final TypeStruct S2F = make("S2"); public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", TypeInteger.BOT), Field.make("s2",TypeMemPtr.make(S2F,false)) }); - private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeFloat .BOT), Field.make("s1",TypeMemPtr.make(S1F,false)) }); + private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeInteger.BOT), Field.make("s1",TypeMemPtr.make(S1F,false)) }); public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); } public int find(String fname) { diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 6f094cfb..00000000 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; } - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 7aab9752..5f92c9c1 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,532 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } - } - } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } - } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; return 0; return 0; ]", stop.toString()); - } - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; } -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; } -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( - """ -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); - } - - @Test - public void testSplit() { - Parser parser = new Parser( - """ -struct S { int f; } -S s = new S; -if( arg==0 ) s.f = 1; -else if (arg == 1) s.f = 1; -return s.f; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); - assertEquals(1L, Evaluator.evaluate(stop, 0)); - assertEquals(1L, Evaluator.evaluate(stop, 1)); - assertEquals(0L, Evaluator.evaluate(stop, 2)); } } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java index ced5606c..cbb50dba 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -2,83 +2,199 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.TypeStruct; +import com.seaofnodes.simple.type.TypeMemPtr; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); - assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + + @Test + public void testLinkedList0() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var stop = new Parser(src).parse().iterate(); - assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); - } + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( next==null ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 3)); } + @Test + public void testCoRecur() { + Parser parser = new Parser( +""" +struct Left { int i; Right? f; } +struct Right { int f; Left? i; } +Left left = new Left; +left.i = 17; +Right right = new Right; +right.f = 314; +left.f = right; +right.i = left; +return right.i.f.i.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 17;", stop.toString()); + } @Test - public void testJig() { + public void testNullReright() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return null;", stop.toString()); } @Test - public void testFloat() { + public void testNullRef1() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +n.next = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return new N;", stop.toString()); } @Test - public void testSquareRoot() { + public void testNullRef2() { Parser parser = new Parser( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct N { N next; int i; } +N n = new N; +n.next = null; +return n.next; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + } + + + @Test + public void testNullRef4() { + Parser parser = new Parser("-null-5/null-5"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test public void testNullRef5() { + Parser parser = new Parser("return null+42;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test + public void testEmpty() { + Parser parser = new Parser( +""" +struct S{}; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,(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)); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testForwardReright() { + Parser parser = new Parser( +""" +struct S1 { S2 s; } +return new S2; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { Parser parser = new Parser( """ -flt x = arg; -return x+1==x; +struct S1 { S2? s; } +struct S2 { int x; } +return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); + assertEquals("return new S1;", stop.toString()); } + @Test + public void testcheckNull() { + Parser parser = new Parser( +""" +struct I {int i;} +struct P {I pi;} +P p1 = new P; +P p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + } + + + @Test + public void testShallowRecursiveType() { + new Parser("struct LLI { LLI? next; int i; } return 0;").parse().iterate(); + TypeStruct l1 = (TypeStruct)Parser.TYPES.get("LLI"); + TypeStruct l0 = ((TypeMemPtr)l1._fields[0]._type)._obj; + assertEquals("LLI", l0._name); + assertNull(l0._fields); + assertEquals(2, l1._fields.length); + assertSame(l0, l1.meet(l0)); + assertSame(l0, l0.dual()); + assertSame(l0, l0.glb()); + } + + @Test + public void testUnusedForwardReference() { + StopNode stop = new Parser("struct Holder { Missing? ref; } Holder h = new Holder; return h.ref;") + .parse().iterate(); + assertEquals("return null;", stop.toString()); + assertNull(((TypeStruct)Parser.TYPES.get("Missing"))._fields); + } } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 13ac58f4..2929d1a4 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,197 +1,532 @@ package com.seaofnodes.simple; + import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; + + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter13Test { + + // A placeholder test used to rapidly rotate through fuzzer produced issues @Test - public void testJig() { + public void testFuzzer() { Parser parser = new Parser( """ -return 3.14; +while(-----arg) { + while(-arg) { + while(-----arg*---0) { + int N=--false==false<--arg; + while(N<-4==N) { + N=N+N; + } + } + { + int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); + while(kzEl_r7EYz<---1) { + kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; + if(7) continue; + kzEl_r7EYz=false; + if(-kzEl_r7EYz+arg<-3) continue; + else { + while(kzEl_r7EYz) continue; + arg=----3; + kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); + int v2uF_4ba=9; + } + } + } + int pjcp7rv=arg; + { + int m2ya3=--arg-pjcp7rv; + while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { + m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); + while(arg<-arg) { + arg=arg+--arg; + { + while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); + pjcp7rv=11; + int c=pjcp7rv; + if(--107) break; + else break; + } + } + } + pjcp7rv=pjcp7rv; + int aR0Wkua=-pjcp7rv==pjcp7rv; + { + if(-0!=-0) while(-true) break; + else { + while(aR0Wkua) pjcp7rv=-0/pjcp7rv; + pjcp7rv=pjcp7rv; + } + } + } + if(-arg) { + { + int Isdlt0fr91=0!=arg; + while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { + Isdlt0fr91=Isdlt0fr91+-1!=true; + if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; + int i=---arg<---arg!=--40; + { + int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; + while(jVX43Ln4Dm<--(3-3)) { + jVX43Ln4Dm=jVX43Ln4Dm+-0<25; + i=24<--(jVX43Ln4Dm); + } + } + i=(arg!=----false>=7); + int Te=5>-arg; + break; + } + } + arg=arg!=--true/arg; + { + int UX=arg; + while(UX<-3*arg) { + UX=UX+arg/-1; + arg=-3--(-UX--47=-arg; + if(---12) while(-UX) break; + break; + } + } + continue; + } + if(arg) arg=--0; + else if(1) continue; + int DtTlhjYq=19==arg; + { + arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); + if(---arg) continue; + else { + int Zsl=40; + while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { + Zsl=Zsl+arg; + } + } + if((arg)) if(-DtTlhjYq) { + } + else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; + else { + int lwbF=arg>=0; + while(lwbF<57) { + lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; + arg=----0+0; + while(49) break; + if(true) DtTlhjYq=-----arg; + DtTlhjYq=0!=-----(0); + } + } + arg=---arg!=DtTlhjYq/--0; + DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; + else { + } + DtTlhjYq=-0; + { + while((0)<-arg) DtTlhjYq=-DtTlhjYq; + } + if(30/--(arg)>=0) DtTlhjYq=-0>=arg; + else { + int Y2wcIQJ=--DtTlhjYq*---11; + if(arg==arg) { + Y2wcIQJ=-----Y2wcIQJ; + Y2wcIQJ=Y2wcIQJ<7; + if(DtTlhjYq) arg=arg; + else break; + while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; + } + continue; + } + arg=DtTlhjYq; + arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; + arg=51+---(-true)!=arg!=----1==-DtTlhjYq; + if(11<=arg) if((arg)+79<32+(-arg)) continue; + else if(true==DtTlhjYq) break; + break; + } +} +if(arg!=62==arg) { + while(---arg) { + { + int bhmDacPM=arg/arg; + while(bhmDacPM1)=-arg+-arg; + break; + } + } + int arg=true; + arg=arg<=arg; + { + int KlC=30; + while(KlC<-2) { + KlC=KlC+-1!=25<=-KlC>KlC>----0; + int Ij=arg>=-arg<=---10; + { + int LAbgurVa2R=-arg!=Ij; + while(LAbgurVa2R<-true) { + LAbgurVa2R=LAbgurVa2R+Ij; + KlC=-LAbgurVa2R; + Ij=42*(-Ij>true); + while(Ij) arg=KlC; + KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; + } + } + int a=Ij<=-KlC; + if(Ij) { + int r1W=a!=-false; + } + else { + arg=56>=Ij; + while(54) break; + arg=8>=-(a<--Ij)/a>=-a-KlC; + } + int Yt_T=KlC; + arg=KlC; + } + } + if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; + else if(----arg) continue; + if(-arg-arg) { + arg=-(-arg)*--(-3)<(-7==-arg----arg); + { + int arg=arg; + while(arg<-0>arg==-arg-(-arg>1+11)) { + arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; + } + } + arg=-arg+arg; + arg=--47+-arg<1>arg*(-arg==-arg--0>3); + arg=(----arg)!=arg+--59; + arg=--4+arg==-(arg)-arg; + } + else { + while(--true-----true*-25+-18>arg>=-----(-arg)---13) { + arg=-0+--true=--0))<=-0/-arg; + int L=-arg; + int B4mHcrf=---vzXB; + int oZWGdzZl=-o3ypEsewxM; + int K6tQqa1=11; + while(0!=--L) { + int sdZfTM=vzXB*true+K6tQqa1; + while(sdZfTMfalse); + sdZfTM=vzXB; + } + } + L=false; + while(31) B4mHcrf=arg; + oZWGdzZl=-0<--oZWGdzZl<=--2; + if(K6tQqa1>=20<=46) { + if(-1+-L) if(oZWGdzZl) { + int wmhv=-----0; + } + int arg=0; + o3ypEsewxM=---(oZWGdzZl); + return -oZWGdzZl/0; + } + else if(arg) { + int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; + return -K6tQqa1; + } + else { + if(---0) { + {} + {} + } + return o3ypEsewxM; + } +} """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("Stop[ return 9; return 0; return 0; return 0; return 0; ]", stop.toString()); } + @Test - public void testLinkedList0() { + public void testPrimes() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; } -return head.next.i; +return primeCount; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below + assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 + assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 + assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 + assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 + assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 } @Test - public void testLinkedList1() { + public void testAntiDeps1() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -LLI? next = head.next; -if( next==null ) return 1; -return next.i; +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 3)); + assertEquals("return .f;", stop.toString()); } @Test - public void testCoRecur() { + public void testAntiDeps2() { Parser parser = new Parser( """ -struct int0 { int i; flt0? f; } -struct flt0 { flt f; int0? i; } -int0 i0 = new int0; -i0.i = 17; -flt0 f0 = new flt0; -f0.f = 3.14; -i0.f = f0; -f0.i = i0; -return f0.i.f.i.i; +struct S { int f; } +S v = new S; +S t = new S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 17;", stop.toString()); + assertEquals("return Phi(Region37,.f,0);", stop.toString()); } @Test - public void testNullRef0() { + public void testAntiDeps3() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -return n.next; +struct S { int f; } +S v0 = new S; +S? v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return null;", stop.toString()); + assertEquals("return new S;", stop.toString()); } + @Test - public void testNullRef1() { + public void testAntiDeps4() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = new N; -return n.next; +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return new N;", stop.toString()); + assertEquals("return .f;", stop.toString()); } @Test - public void testNullRef2() { + public void testAntiDeps5() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = null; -return n.next; +struct S { int f; } +S v = new S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return new S;", stop.toString()); } @Test - public void testNullRef3() { + public void testAntiDeps6() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.i = 3.14; -return n.i; +struct s { int v; } +s ptr=new s; +while( -arg ) + ptr = new s; +while(1) + arg = arg+ptr.v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field IntBot i",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 0; ]", stop.toString()); } @Test - public void testNullRef4() { - Parser parser = new Parser("-null-5/null-5"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } - } - - @Test public void testNullRef5() { - Parser parser = new Parser("return null+42;"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + public void testAntiDeps7() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return .f;", stop.toString()); } @Test - public void testEmpty() { + public void testAntiDeps8() { Parser parser = new Parser( """ -struct S{}; +struct S { int f; } +S v = new S; +S t = new S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 0)); + assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); } @Test - public void testForwardRef0() { + public void testAntiDeps9() { Parser parser = new Parser( """ -struct S1 { S2 s; } -return new S2; +struct S { int f; } +S v = new S; +S t = new S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return new S;", stop.toString()); } @Test - public void testForwardRef1() { + public void testExample2() { Parser parser = new Parser( -""" -struct S1 { S2? s; } -struct S2 { int x; } -return new S1.s=new S2; -"""); + """ +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); StopNode stop = parser.parse().iterate(); - assertEquals("return new S1;", stop.toString()); + //assertEquals("return new S;", stop.toString()); } @Test - public void testcheckNull() { + public void testSplit() { Parser parser = new Parser( -""" -struct I {int i;} -struct P {I pi;} -P p1 = new P; -P p2 = new P; -p2.pi = new I; -p2.pi.i = 2; -if (arg) p1 = new P; -return p1.pi.i + 1; + """ +struct S { int f; } +S s = new S; +if( arg==0 ) s.f = 1; +else if (arg == 1) s.f = 1; +return s.f; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + assertEquals(0L, Evaluator.evaluate(stop, 2)); } - } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 40a2f78d..355d906b 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -2,7 +2,10 @@ package com.seaofnodes.simple.evaluator; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.TypeStruct; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -77,10 +80,6 @@ public class Evaluator { long in2 = vall(div.in(2)); return in2 == 0 ? 0 : vall(div.in(1)) / in2; } - private double div(DivFNode div) { - double in2 = vald(div.in(2)); - return in2 == 0 ? 0 : vald(div.in(1)) / in2; - } private Object alloc(NewNode alloc) { var type = ((TypeMemPtr)alloc.compute())._obj; @@ -116,16 +115,10 @@ public class Evaluator { if (v instanceof Number n) return n.longValue(); throw new AssertionError("Not a long " + v); } - private double vald(Node node) { - var v = val(node); - if (v instanceof Number n) return n.doubleValue(); - throw new AssertionError("Not a double " + v); - } private Object cons(ConstantNode cons) { var type = cons.compute(); if (type instanceof TypeInteger i) return i.value(); - if (type instanceof TypeFloat i) return i.value(); assert type instanceof TypeMemPtr; return null; } @@ -134,24 +127,15 @@ public class Evaluator { return switch (node) { case ConstantNode cons -> cons(cons); case AddNode add -> vall(add.in(1)) + vall(add.in(2)); - case AddFNode add -> vald(add.in(1)) + vald(add.in(2)); - case BoolNode.EQF eq -> vald(eq.in(1)) == vald(eq.in(2)) ? 1L : 0L; - case BoolNode.LEF le -> vald(le.in(1)) <= vald(le.in(2)) ? 1L : 0L; - case BoolNode.LTF lt -> vald(lt.in(1)) < vald(lt.in(2)) ? 1L : 0L; case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; case DivNode div -> div(div); - case DivFNode div -> div(div); case MinusNode minus -> -vall(minus.in(1)); - case MinusFNode minus -> -vald(minus.in(1)); case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); - case MulFNode mul -> vald(mul.in(1)) * vald(mul.in(2)); case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); - case SubFNode sub -> vald(sub.in(1)) - vald(sub.in(2)); case CastNode cast -> val(cast.in(1)); - case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); case MemMergeNode merge -> MEMORY; diff --git a/chapter13/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter13/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 94929241..14baadba 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -87,21 +87,7 @@ public class ScriptGenerator { } - private static final Type TYPE_FLT = new Type("flt"); - - private static class TypeInt extends Type { - - TypeInt() { - super("int"); - } - - @Override - boolean isa(Type other) { - return super.isa(other) || other == TYPE_FLT; - } - } - - private static final Type TYPE_INT = new TypeInt(); + private static final Type TYPE_INT = new Type("int"); private static class Variable { @@ -368,7 +354,6 @@ public class ScriptGenerator { private Type getType(boolean allowForward) { int t = random.nextInt(10); if( t<5 || structs.isEmpty() ) return TYPE_INT; - if( t<7 ) return TYPE_FLT; if (allowForward && random.nextInt(10)==0) { int i=random.nextInt(forwardStructs.size()+1); if (i>0) return forwardStructs.get(i-1).nullable; @@ -725,7 +710,7 @@ public class ScriptGenerator { */ public void genExpression(Type type, boolean change) { if (change && generateInvalid()) type = getType(); - if (type != TYPE_INT && type != TYPE_FLT) { + if (type != TYPE_INT) { genUnary(type, false); return; } @@ -742,7 +727,7 @@ public class ScriptGenerator { */ public void genUnary(Type type, boolean change) { if (change && generateInvalid()) type = getType(); - if (type != TYPE_INT && type != TYPE_FLT) { + if (type != TYPE_INT) { genSuffix(type, false); return; } @@ -822,15 +807,14 @@ public class ScriptGenerator { */ public void genConst(Type type) { if (generateInvalid()) type = getType(); - if (type == TYPE_INT || (type == TYPE_FLT && random.nextBoolean())) { + if (type == TYPE_INT) { var rand = random.nextInt(10); switch (rand) { case 0 -> sb.append("true"); case 1 -> sb.append("false"); default -> sb.append(random.nextInt(1<<(rand-2))); } - } else if (type == TYPE_FLT) { - sb.append(random.nextFloat()); + } else if (type instanceof TypeNullable n) { if ((n.base instanceof TypeStruct s && s.fields == null) || random.nextBoolean()) { sb.append("null"); diff --git a/chapter14/README.md b/chapter14/README.md index 50009f4a..84207f52 100644 --- a/chapter14/README.md +++ b/chapter14/README.md @@ -1,27 +1,79 @@ -# Chapter 14: Narrow types +# Chapter 14: Numeric Types [Previous: Chapter 13](../chapter13/README.md) | [Next: Chapter 15](../chapter15/README.md) # Table of Contents -1. [Narrow Word Types](#narrow-or-sub-word-types) -2. [Overflow handling](#overflow-handling) -3. [Integer ranges](#integer-ranges) -4. [Bitwise operations](#bitwise-operations) -5. [Precedence](#precedence) -6. [Type lattice](#type-implementation) -7. [Nodes](#nodes) +1. [Floating-point arithmetic](#floating-point-arithmetic) +2. [Narrow Word Types](#narrow-or-sub-word-types) +3. [Overflow handling](#overflow-handling) +4. [Integer ranges](#integer-ranges) +5. [Bitwise operations](#bitwise-operations) +6. [Precedence](#precedence) +7. [Type lattice](#type-implementation) +8. [Nodes](#nodes) -In this chapter, we add sub-word integer types. +With structs, reference fields, and scheduling in place, this chapter extends +the numeric values they carry. We add floating-point arithmetic, narrow +integer and float types, and integer ranges. You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter14) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter13...linear-chapter14) it to the previous chapter. -## Narrow or sub-word types +## Floating-point arithmetic + +Floating point values start with the `flt` type and compute IEEE754 64-bit arithmetic. +Integer expressions auto-widen to floats when involved in a float expression. + +```java +flt f = 0.5; +int x = 2; +f = f+x; // Auto-widen x to 2.0, result is 2.5 +f = f+true; // Since bools are just synonyms for integers 0,1 they auto-widen also +``` -Narrow or sub-word integer types refer to integer data types that occupy less memory (fewer bits) than the standard or "full-width" (typically 32 or 64 bits) integer types provided by a system or programming language. +There is no corresponding way right now to round a `flt` back to an `int`: +```java +int x = 3.14; // error +``` + +Here is Newton's method for computing a square root: +```java +flt guess = arg; // Initial guess is just argument +while( true ) { + // Next guess from Newton's method + flt next = (arg/guess + guess)/2; + // Break if hit fixed point + if( next == guess ) break; + guess = next; +} +return guess; +``` +When run with argument 2, produces 1.414213562373095. +Floating point operations have their own Nodes: + +| Node | Op | +|------------------------|----------------------| +| AddF, SubF, MulF, DivF | Basic binary FP ops | +| MinusF | Unary negate | +| EQF, LTF, LEF | FP compare operators | + +The parser uses [`Node.widen()`](src/main/java/com/seaofnodes/simple/node/Node.java) +to choose the floating-point operation and insert `ToFloatNode` conversions. +The float lattice has constants between `TOP` and `BOT`, like the integer +lattice before ranges. [`TypeFloat`](src/main/java/com/seaofnodes/simple/type/TypeFloat.java) +also records whether a value fits `f32`; assigning to `f32` rounds it to that +precision. Conversion from float back to integer remains a separate, pending +language feature. + + +## Narrow or sub-word types + +Narrow or sub-word integer types refer to integer data types that occupy less +memory (fewer bits) than the standard or "full-width" (typically 32 or 64 bits) +integer types provided by a system or programming language. As a prelude to arrays, especially arrays of bytes common in all networking codes, Simple needs some way to manipulate sub-word integer types. @@ -209,11 +261,11 @@ This is the same as regular additions: public long value() { assert isConstant(); return _min; } ``` -#### Truncate(2) +#### More Truncate ```java u8 a = arg; // arg & 255 -u8 b = arg; // arg & 255(same as a) +u8 b = arg; // arg & 255 (same as a) u8 c = a + b; // (arg&255)*2, type = TypeInteger.BOT return c; // (((arg&255)*2)&255), type = TypeInteger.U8 ``` @@ -224,9 +276,9 @@ on the expression to keep it in the valid boundaries. ## Integer ranges -Link hacker's delight here. -For range math, visit:[^1] +For general range math, visit Hackers Delight:[^1] + ## Bitwise operations We support the following bitwise operators: And `&`, Or `|`, Xor `^`, Shift left `<<`, Shift right `>>`, Shift right zero `>>>` @@ -242,6 +294,8 @@ And `&`, Or `|`, Xor `^`, Shift left `<<`, Shift right `>>`, Shift right zero `> Note `>>>` is a logical shift and not an arithmetic shift. +Here is a sample of peephole opts on these logical operations: + #### AndNode: ##### lhs & -1 = lhs; @@ -305,7 +359,7 @@ arg + 123 & 3 = (arg + 123) & 3 ## Type Implementation -The ![Integer](../chapter10a/docs/type-integer.svg) `TypeInteger` class is +The ![Integer](../chapter10/docs/type-integer.svg) `TypeInteger` class is reworked to support a full range of min/max values. At this time, only some power-of-2 sized ranges are exposed to the programmer but the optimizer internally supports all ranges. The MEET operation takes the min-of-mins and @@ -321,17 +375,18 @@ Example: the `i8` type has the range `[-128...127]`. Example: the `dual` of `bool` is `[1...0]` (just swap min and max). -The ![Float](../docs/type-float.svg) `TypeFloat` class is also reworked to support 32-bit and 64-bit sizes. +The ![Float](../docs/type-float.svg) `TypeFloat` class introduced here supports +both 32-bit and 64-bit sizes. The integer range diagram is schematic: it shows the boolean range and its -dual as examples among the possible ranges. The other domains retain the +dual as examples among the possible ranges. The other domains retain the named structs, nullable pointers, and precise memory aliases from earlier chapters. ![Graph1](./docs/lattice.svg) ### meet: -Meet of two ranges: +The Meet of two ranges: ```java @Override public Type xmeet(Type other) { @@ -340,17 +395,14 @@ TypeInteger i = (TypeInteger)other; // Contract return make(Math.min(_min,i._min), Math.max(_max,i._max)); } ``` -Combine all values that could possibly occur within either range. -## Nodes - -Added this Chapter are some bit-manipulation Nodes representing common hardware -ops to mask, shift, sign-extend and round bits. The truncation and extension -logic uses these. They are otherwise very similar to the previous -`Add/Sub/Mul/Div` Nodes but with slightly different constant math and -slightly different `idealize()` calls. +This combines all values that could possibly occur within either range. +Also, the implementation is trivial. [^1]: Hacker's delight. 4-2 Propagating Bounds through Adds and Subtracts + +The next chapter uses these numeric types for array lengths, indexing, and +element storage, including byte arrays. diff --git a/chapter14/chapter14.iml b/chapter14/chapter14.iml new file mode 100644 index 00000000..4c719b6d --- /dev/null +++ b/chapter14/chapter14.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chapter14/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter14/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 2599a383..8ad59a39 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -8,14 +8,12 @@ import java.util.ArrayList; */ public class TypeStruct extends Type { - // A Struct has a name and a set of fields; the fields themselves have - // names and types. Briefly during parsing its allowed to have a - // forward-ref to a Struct; in this case the _fields array is null. - // Its illegal to attempt to load a field from a forward-ref struct. - // - // During the normal optimization run, struct types "bottom out" at further - // struct references, so we don't have to handle e.g. cyclic types. The - // "bottom out" is again the forward-ref struct. + // Immutable shallow references, resolved by name in Parser.TYPES when needed. + // A name-only struct has _fields == null, even after its definition is known. + // For LLI { LLI? next; int i; }, L0 is name-only and L1.next points to L0; + // TYPES["LLI"] holds L1. We deliberately stop recursive type expansion here, + // keeping interning and lattice operations finite and acyclic. Never patch + // L0 into L1: field access looks up the complete declaration by name instead. public final String _name; public final Field[] _fields; diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 6f094cfb..00000000 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; } - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java index d2e300b1..5f92c9c1 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,532 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } - } - } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } - } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); - } - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; } -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; } -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return .f;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return new S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( - """ -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); - } - - @Test - public void testSplit() { - Parser parser = new Parser( - """ -struct S { int f; } -S s = new S; -if( arg==0 ) s.f = 1; -else if (arg == 1) s.f = 1; -return s.f; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); - assertEquals(1L, Evaluator.evaluate(stop, 0)); - assertEquals(1L, Evaluator.evaluate(stop, 1)); - assertEquals(0L, Evaluator.evaluate(stop, 2)); } } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 998053ec..e84da36a 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -2,82 +2,221 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.TypeStruct; +import com.seaofnodes.simple.type.TypeMemPtr; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); - assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var stop = new Parser(src).parse().iterate(); - assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); - } + @Test + public void testLinkedList0() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + } + + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( next==null ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 3)); } + @Test + public void testCoRecur() { + Parser parser = new Parser( +""" +struct int0 { int i; flt0? f; } +struct flt0 { flt f; int0? i; } +int0 i0 = new int0; +i0.i = 17; +flt0 f0 = new flt0; +f0.f = 3.14; +i0.f = f0; +f0.i = i0; +return f0.i.f.i.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 17;", stop.toString()); + } @Test - public void testJig() { + public void testNullRef0() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return null;", stop.toString()); } @Test - public void testFloat() { + public void testNullRef1() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +n.next = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return new N;", stop.toString()); } @Test - public void testSquareRoot() { + public void testNullRef2() { Parser parser = new Parser( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct N { N next; int i; } +N n = new N; +n.next = null; +return n.next; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + } + + @Test + public void testNullRef3() { + Parser parser = new Parser( +""" +struct N { N next; int i; } +N n = new N; +n.i = 3.14; +return n.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + } + + @Test + public void testNullRef4() { + Parser parser = new Parser("-null-5/null-5"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test public void testNullRef5() { + Parser parser = new Parser("return null+42;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test + public void testEmpty() { + Parser parser = new Parser( +""" +struct S{}; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,(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)); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testForwardRef0() { + Parser parser = new Parser( +""" +struct S1 { S2 s; } +return new S2; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { Parser parser = new Parser( """ -flt x = arg; -return x+1==x; +struct S1 { S2? s; } +struct S2 { int x; } +return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); + assertEquals("return new S1;", stop.toString()); + } + + @Test + public void testcheckNull() { + Parser parser = new Parser( +""" +struct I {int i;} +struct P {I pi;} +P p1 = new P; +P p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + } + + + @Test + public void testShallowRecursiveType() { + new Parser("struct LLI { LLI? next; int i; } return 0;").parse().iterate(); + TypeStruct l1 = (TypeStruct)Parser.TYPES.get("LLI"); + TypeStruct l0 = ((TypeMemPtr)l1._fields[0]._type)._obj; + assertEquals("LLI", l0._name); + assertNull(l0._fields); + assertEquals(2, l1._fields.length); + assertSame(l0, l1.meet(l0)); + assertSame(l0, l0.dual()); + assertSame(l0, l0.glb()); + } + + @Test + public void testUnusedForwardReference() { + StopNode stop = new Parser("struct Holder { Missing? ref; } Holder h = new Holder; return h.ref;") + .parse().iterate(); + assertEquals("return null;", stop.toString()); + assertNull(((TypeStruct)Parser.TYPES.get("Missing"))._fields); } } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 15f97b79..34d71d9b 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,197 +1,532 @@ package com.seaofnodes.simple; + import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; + + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter13Test { + + // A placeholder test used to rapidly rotate through fuzzer produced issues @Test - public void testJig() { + public void testFuzzer() { Parser parser = new Parser( """ -return 3.14; +while(-----arg) { + while(-arg) { + while(-----arg*---0) { + int N=--false==false<--arg; + while(N<-4==N) { + N=N+N; + } + } + { + int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); + while(kzEl_r7EYz<---1) { + kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; + if(7) continue; + kzEl_r7EYz=false; + if(-kzEl_r7EYz+arg<-3) continue; + else { + while(kzEl_r7EYz) continue; + arg=----3; + kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); + int v2uF_4ba=9; + } + } + } + int pjcp7rv=arg; + { + int m2ya3=--arg-pjcp7rv; + while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { + m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); + while(arg<-arg) { + arg=arg+--arg; + { + while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); + pjcp7rv=11; + int c=pjcp7rv; + if(--107) break; + else break; + } + } + } + pjcp7rv=pjcp7rv; + int aR0Wkua=-pjcp7rv==pjcp7rv; + { + if(-0!=-0) while(-true) break; + else { + while(aR0Wkua) pjcp7rv=-0/pjcp7rv; + pjcp7rv=pjcp7rv; + } + } + } + if(-arg) { + { + int Isdlt0fr91=0!=arg; + while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { + Isdlt0fr91=Isdlt0fr91+-1!=true; + if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; + int i=---arg<---arg!=--40; + { + int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; + while(jVX43Ln4Dm<--(3-3)) { + jVX43Ln4Dm=jVX43Ln4Dm+-0<25; + i=24<--(jVX43Ln4Dm); + } + } + i=(arg!=----false>=7); + int Te=5>-arg; + break; + } + } + arg=arg!=--true/arg; + { + int UX=arg; + while(UX<-3*arg) { + UX=UX+arg/-1; + arg=-3--(-UX--47=-arg; + if(---12) while(-UX) break; + break; + } + } + continue; + } + if(arg) arg=--0; + else if(1) continue; + int DtTlhjYq=19==arg; + { + arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); + if(---arg) continue; + else { + int Zsl=40; + while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { + Zsl=Zsl+arg; + } + } + if((arg)) if(-DtTlhjYq) { + } + else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; + else { + int lwbF=arg>=0; + while(lwbF<57) { + lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; + arg=----0+0; + while(49) break; + if(true) DtTlhjYq=-----arg; + DtTlhjYq=0!=-----(0); + } + } + arg=---arg!=DtTlhjYq/--0; + DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; + else { + } + DtTlhjYq=-0; + { + while((0)<-arg) DtTlhjYq=-DtTlhjYq; + } + if(30/--(arg)>=0) DtTlhjYq=-0>=arg; + else { + int Y2wcIQJ=--DtTlhjYq*---11; + if(arg==arg) { + Y2wcIQJ=-----Y2wcIQJ; + Y2wcIQJ=Y2wcIQJ<7; + if(DtTlhjYq) arg=arg; + else break; + while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; + } + continue; + } + arg=DtTlhjYq; + arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; + arg=51+---(-true)!=arg!=----1==-DtTlhjYq; + if(11<=arg) if((arg)+79<32+(-arg)) continue; + else if(true==DtTlhjYq) break; + break; + } +} +if(arg!=62==arg) { + while(---arg) { + { + int bhmDacPM=arg/arg; + while(bhmDacPM1)=-arg+-arg; + break; + } + } + int arg=true; + arg=arg<=arg; + { + int KlC=30; + while(KlC<-2) { + KlC=KlC+-1!=25<=-KlC>KlC>----0; + int Ij=arg>=-arg<=---10; + { + int LAbgurVa2R=-arg!=Ij; + while(LAbgurVa2R<-true) { + LAbgurVa2R=LAbgurVa2R+Ij; + KlC=-LAbgurVa2R; + Ij=42*(-Ij>true); + while(Ij) arg=KlC; + KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; + } + } + int a=Ij<=-KlC; + if(Ij) { + int r1W=a!=-false; + } + else { + arg=56>=Ij; + while(54) break; + arg=8>=-(a<--Ij)/a>=-a-KlC; + } + int Yt_T=KlC; + arg=KlC; + } + } + if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; + else if(----arg) continue; + if(-arg-arg) { + arg=-(-arg)*--(-3)<(-7==-arg----arg); + { + int arg=arg; + while(arg<-0>arg==-arg-(-arg>1+11)) { + arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; + } + } + arg=-arg+arg; + arg=--47+-arg<1>arg*(-arg==-arg--0>3); + arg=(----arg)!=arg+--59; + arg=--4+arg==-(arg)-arg; + } + else { + while(--true-----true*-25+-18>arg>=-----(-arg)---13) { + arg=-0+--true=--0))<=-0/-arg; + int L=-arg; + int B4mHcrf=---vzXB; + int oZWGdzZl=-o3ypEsewxM; + int K6tQqa1=11; + while(0!=--L) { + int sdZfTM=vzXB*true+K6tQqa1; + while(sdZfTMfalse); + sdZfTM=vzXB; + } + } + L=false; + while(31) B4mHcrf=arg; + oZWGdzZl=-0<--oZWGdzZl<=--2; + if(K6tQqa1>=20<=46) { + if(-1+-L) if(oZWGdzZl) { + int wmhv=-----0; + } + int arg=0; + o3ypEsewxM=---(oZWGdzZl); + return -oZWGdzZl/0; + } + else if(arg) { + int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; + return -K6tQqa1; + } + else { + if(---0) { + {} + {} + } + return o3ypEsewxM; + } +} """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); } + @Test - public void testLinkedList0() { + public void testPrimes() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; } -return head.next.i; +return primeCount; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below + assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 + assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 + assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 + assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 + assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 } @Test - public void testLinkedList1() { + public void testAntiDeps1() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -LLI? next = head.next; -if( next==null ) return 1; -return next.i; +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 3)); + assertEquals("return .f;", stop.toString()); } @Test - public void testCoRecur() { + public void testAntiDeps2() { Parser parser = new Parser( """ -struct int0 { int i; flt0? f; } -struct flt0 { flt f; int0? i; } -int0 i0 = new int0; -i0.i = 17; -flt0 f0 = new flt0; -f0.f = 3.14; -i0.f = f0; -f0.i = i0; -return f0.i.f.i.i; +struct S { int f; } +S v = new S; +S t = new S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 17;", stop.toString()); + assertEquals("return Phi(Region37,.f,0);", stop.toString()); } @Test - public void testNullRef0() { + public void testAntiDeps3() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -return n.next; +struct S { int f; } +S v0 = new S; +S? v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return null;", stop.toString()); + assertEquals("return new S;", stop.toString()); } + @Test - public void testNullRef1() { + public void testAntiDeps4() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = new N; -return n.next; +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return new N;", stop.toString()); + assertEquals("return .f;", stop.toString()); } @Test - public void testNullRef2() { + public void testAntiDeps5() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = null; -return n.next; +struct S { int f; } +S v = new S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return new S;", stop.toString()); } @Test - public void testNullRef3() { + public void testAntiDeps6() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.i = 3.14; -return n.i; +struct s { int v; } +s ptr=new s; +while( -arg ) + ptr = new s; +while(1) + arg = arg+ptr.v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 0; ]", stop.toString()); } @Test - public void testNullRef4() { - Parser parser = new Parser("-null-5/null-5"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } - } - - @Test public void testNullRef5() { - Parser parser = new Parser("return null+42;"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + public void testAntiDeps7() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return .f;", stop.toString()); } @Test - public void testEmpty() { + public void testAntiDeps8() { Parser parser = new Parser( """ -struct S{}; +struct S { int f; } +S v = new S; +S t = new S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 0)); + assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); } @Test - public void testForwardRef0() { + public void testAntiDeps9() { Parser parser = new Parser( """ -struct S1 { S2 s; } -return new S2; +struct S { int f; } +S v = new S; +S t = new S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return new S;", stop.toString()); } @Test - public void testForwardRef1() { + public void testExample2() { Parser parser = new Parser( -""" -struct S1 { S2? s; } -struct S2 { int x; } -return new S1.s=new S2; -"""); + """ +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); StopNode stop = parser.parse().iterate(); - assertEquals("return new S1;", stop.toString()); + //assertEquals("return new S;", stop.toString()); } @Test - public void testcheckNull() { + public void testSplit() { Parser parser = new Parser( -""" -struct I {int i;} -struct P {I pi;} -P p1 = new P; -P p2 = new P; -p2.pi = new I; -p2.pi.i = 2; -if (arg) p1 = new P; -return p1.pi.i + 1; + """ +struct S { int f; } +S s = new S; +if( arg==0 ) s.f = 1; +else if (arg == 1) s.f = 1; +return s.f; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + assertEquals(0L, Evaluator.evaluate(stop, 2)); } - } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java new file mode 100644 index 00000000..f2b51da2 --- /dev/null +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -0,0 +1,95 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter14FloatTest { + @Test public void testSubZeroFloat() { + var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); + // Compare strings so +0.0 and -0.0 remain distinct. + assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); + assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + } + + @Test public void testDeadNumericReturns() { + for( String[] test : new String[][] { + {"if(1) return 7; else return 2.5;", "7"}, + {"if(0) return 7; else return 2.5;", "2.5"}, + {"return 7; return 2.5;", "7"}, + {"return 2.5; return 7;", "2.5"} + } ) { + String src = test[0]; + var stop = new Parser(src).parse().iterate(); + assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); + } + } + + + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testFloat() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testSquareRoot() { + Parser parser = new Parser( +""" +flt guess = arg; +while( 1 ) { + flt next = (arg/guess + guess)/2; + if( next == guess ) break; + guess = next; +} +return guess; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,(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)); + } + + @Test + public void testFPOps() { + Parser parser = new Parser( +""" +flt x = arg; +return x+1==x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); + } + @Test + public void testReferenceFieldRejectsFloat() { + Parser parser = new Parser( +""" +struct N { N next; int i; } +N n = new N; +n.i = 3.14; +return n.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + } +} diff --git a/chapter15/README.md b/chapter15/README.md index 1485b04c..db0f2f1c 100644 --- a/chapter15/README.md +++ b/chapter15/README.md @@ -89,7 +89,7 @@ These `TypeStruct` arrays otherwise behave exactly as a `TypeStruct`. ## Allocation and partial memory -This chapter keeps Chapter 10b's single hidden `$mem` variable and lazy +This chapter keeps Chapter 11's single hidden `$mem` variable and lazy `BulkMemPhi`/`MemPhi` partitioning. Start produces `{ctrl, $mem, arg}` and Return consumes `{ctrl, $mem, value}`. Array initialization changes New: instead of separate initializing Stores, New allocates and zeros the object. @@ -233,6 +233,7 @@ This means the IR for: Notice the `idx+1` folds the `+1` into the base math. +Also notice this form is perfect for the X86 addressing modes. ## Discussion diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 6f094cfb..00000000 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; } - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; } - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 3421340f..5f92c9c1 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,519 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } - } - } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } - } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); - } - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; } -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 0)); - assertEquals(2L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -v.f = arg; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region46,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; } -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -v.f = arg; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; } -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop21,arg,Phi(Region48,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; } -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( - """ -struct S { int f; } -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); } } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 998053ec..e885deb3 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -8,76 +8,190 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); - assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var stop = new Parser(src).parse().iterate(); - assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); - } + @Test + public void testLinkedList0() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; } +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( next==null ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 3)); + } @Test - public void testJig() { + public void testCoRecur() { Parser parser = new Parser( """ -return 3.14; +struct int0 { int i; flt0? f; } +struct flt0 { flt f; int0? i; } +int0 i0 = new int0; +i0.i = 17; +flt0 f0 = new flt0; +f0.f = 3.14; +i0.f = f0; +f0.i = i0; +return f0.i.f.i.i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return 17;", stop.toString()); } @Test - public void testFloat() { + public void testNullRef0() { Parser parser = new Parser( """ -return 3.14; +struct N { N next; int i; } +N n = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return null;", stop.toString()); } @Test - public void testSquareRoot() { + public void testNullRef1() { Parser parser = new Parser( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct N { N next; int i; } +N n = new N; +n.next = new N; +return n.next; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return N;", stop.toString()); + } + + @Test + public void testNullRef2() { + Parser parser = new Parser( +""" +struct N { N next; int i; } +N n = new N; +n.next = null; +return n.next; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + } + + @Test + public void testNullRef3() { + Parser parser = new Parser( +""" +struct N { N next; int i; } +N n = new N; +n.i = 3.14; +return n.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + } + + @Test + public void testNullRef4() { + Parser parser = new Parser("-null-5/null-5"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test public void testNullRef5() { + Parser parser = new Parser("return null+42;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test + public void testEmpty() { + Parser parser = new Parser( +""" +struct S{}; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,(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)); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testForwardRef0() { + Parser parser = new Parser( +""" +struct S1 { S2 s; } +return new S2; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { Parser parser = new Parser( """ -flt x = arg; -return x+1==x; +struct S1 { S2? s; } +struct S2 { int x; } +return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); + assertEquals("return S2;", stop.toString()); + } + + @Test + public void testcheckNull() { + Parser parser = new Parser( +""" +struct I {int i;} +struct P {I pi;} +P p1 = new P; +P p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } + } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 3313e731..3373a1aa 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,197 +1,519 @@ package com.seaofnodes.simple; + import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; + + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter13Test { + + // A placeholder test used to rapidly rotate through fuzzer produced issues @Test - public void testJig() { + public void testFuzzer() { Parser parser = new Parser( """ -return 3.14; +while(-----arg) { + while(-arg) { + while(-----arg*---0) { + int N=--false==false<--arg; + while(N<-4==N) { + N=N+N; + } + } + { + int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); + while(kzEl_r7EYz<---1) { + kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; + if(7) continue; + kzEl_r7EYz=false; + if(-kzEl_r7EYz+arg<-3) continue; + else { + while(kzEl_r7EYz) continue; + arg=----3; + kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); + int v2uF_4ba=9; + } + } + } + int pjcp7rv=arg; + { + int m2ya3=--arg-pjcp7rv; + while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { + m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); + while(arg<-arg) { + arg=arg+--arg; + { + while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); + pjcp7rv=11; + int c=pjcp7rv; + if(--107) break; + else break; + } + } + } + pjcp7rv=pjcp7rv; + int aR0Wkua=-pjcp7rv==pjcp7rv; + { + if(-0!=-0) while(-true) break; + else { + while(aR0Wkua) pjcp7rv=-0/pjcp7rv; + pjcp7rv=pjcp7rv; + } + } + } + if(-arg) { + { + int Isdlt0fr91=0!=arg; + while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { + Isdlt0fr91=Isdlt0fr91+-1!=true; + if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; + int i=---arg<---arg!=--40; + { + int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; + while(jVX43Ln4Dm<--(3-3)) { + jVX43Ln4Dm=jVX43Ln4Dm+-0<25; + i=24<--(jVX43Ln4Dm); + } + } + i=(arg!=----false>=7); + int Te=5>-arg; + break; + } + } + arg=arg!=--true/arg; + { + int UX=arg; + while(UX<-3*arg) { + UX=UX+arg/-1; + arg=-3--(-UX--47=-arg; + if(---12) while(-UX) break; + break; + } + } + continue; + } + if(arg) arg=--0; + else if(1) continue; + int DtTlhjYq=19==arg; + { + arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); + if(---arg) continue; + else { + int Zsl=40; + while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { + Zsl=Zsl+arg; + } + } + if((arg)) if(-DtTlhjYq) { + } + else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; + else { + int lwbF=arg>=0; + while(lwbF<57) { + lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; + arg=----0+0; + while(49) break; + if(true) DtTlhjYq=-----arg; + DtTlhjYq=0!=-----(0); + } + } + arg=---arg!=DtTlhjYq/--0; + DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; + else { + } + DtTlhjYq=-0; + { + while((0)<-arg) DtTlhjYq=-DtTlhjYq; + } + if(30/--(arg)>=0) DtTlhjYq=-0>=arg; + else { + int Y2wcIQJ=--DtTlhjYq*---11; + if(arg==arg) { + Y2wcIQJ=-----Y2wcIQJ; + Y2wcIQJ=Y2wcIQJ<7; + if(DtTlhjYq) arg=arg; + else break; + while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; + } + continue; + } + arg=DtTlhjYq; + arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; + arg=51+---(-true)!=arg!=----1==-DtTlhjYq; + if(11<=arg) if((arg)+79<32+(-arg)) continue; + else if(true==DtTlhjYq) break; + break; + } +} +if(arg!=62==arg) { + while(---arg) { + { + int bhmDacPM=arg/arg; + while(bhmDacPM1)=-arg+-arg; + break; + } + } + int arg=true; + arg=arg<=arg; + { + int KlC=30; + while(KlC<-2) { + KlC=KlC+-1!=25<=-KlC>KlC>----0; + int Ij=arg>=-arg<=---10; + { + int LAbgurVa2R=-arg!=Ij; + while(LAbgurVa2R<-true) { + LAbgurVa2R=LAbgurVa2R+Ij; + KlC=-LAbgurVa2R; + Ij=42*(-Ij>true); + while(Ij) arg=KlC; + KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; + } + } + int a=Ij<=-KlC; + if(Ij) { + int r1W=a!=-false; + } + else { + arg=56>=Ij; + while(54) break; + arg=8>=-(a<--Ij)/a>=-a-KlC; + } + int Yt_T=KlC; + arg=KlC; + } + } + if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; + else if(----arg) continue; + if(-arg-arg) { + arg=-(-arg)*--(-3)<(-7==-arg----arg); + { + int arg=arg; + while(arg<-0>arg==-arg-(-arg>1+11)) { + arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; + } + } + arg=-arg+arg; + arg=--47+-arg<1>arg*(-arg==-arg--0>3); + arg=(----arg)!=arg+--59; + arg=--4+arg==-(arg)-arg; + } + else { + while(--true-----true*-25+-18>arg>=-----(-arg)---13) { + arg=-0+--true=--0))<=-0/-arg; + int L=-arg; + int B4mHcrf=---vzXB; + int oZWGdzZl=-o3ypEsewxM; + int K6tQqa1=11; + while(0!=--L) { + int sdZfTM=vzXB*true+K6tQqa1; + while(sdZfTMfalse); + sdZfTM=vzXB; + } + } + L=false; + while(31) B4mHcrf=arg; + oZWGdzZl=-0<--oZWGdzZl<=--2; + if(K6tQqa1>=20<=46) { + if(-1+-L) if(oZWGdzZl) { + int wmhv=-----0; + } + int arg=0; + o3ypEsewxM=---(oZWGdzZl); + return -oZWGdzZl/0; + } + else if(arg) { + int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; + return -K6tQqa1; + } + else { + if(---0) { + {} + {} + } + return o3ypEsewxM; + } +} """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); } + @Test - public void testLinkedList0() { + public void testPrimes() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; } -return head.next.i; +return primeCount; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below + assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 + assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 + assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 + assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 + assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 } @Test - public void testLinkedList1() { + public void testAntiDeps1() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -LLI? next = head.next; -if( next==null ) return 1; -return next.i; +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 3)); + assertEquals("return 2;", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 0)); + assertEquals(2L, Evaluator.evaluate(stop, 1)); } @Test - public void testCoRecur() { + public void testAntiDeps2() { Parser parser = new Parser( """ -struct int0 { int i; flt0? f; } -struct flt0 { flt f; int0? i; } -int0 i0 = new int0; -i0.i = 17; -flt0 f0 = new flt0; -f0.f = 3.14; -i0.f = f0; -f0.i = i0; -return f0.i.f.i.i; +struct S { int f; } +S v = new S; +v.f = arg; +S t = new S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 17;", stop.toString()); + assertEquals("return Phi(Region46,.f,0);", stop.toString()); } @Test - public void testNullRef0() { + public void testAntiDeps3() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -return n.next; +struct S { int f; } +S v0 = new S; +S? v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return null;", stop.toString()); + assertEquals("return S;", stop.toString()); } + @Test - public void testNullRef1() { + public void testAntiDeps4() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = new N; -return n.next; +struct S { int f; } +S v = new S; +v.f = arg; +S t = new S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return N;", stop.toString()); + assertEquals("return arg;", stop.toString()); } @Test - public void testNullRef2() { + public void testAntiDeps5() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = null; -return n.next; +struct S { int f; } +S v = new S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return S;", stop.toString()); } @Test - public void testNullRef3() { + public void testAntiDeps6() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.i = 3.14; -return n.i; +struct s { int v; } +s ptr=new s; +while( -arg ) + ptr = new s; +while(1) + arg = arg+ptr.v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } - } - - @Test - public void testNullRef4() { - Parser parser = new Parser("-null-5/null-5"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } - } - - @Test public void testNullRef5() { - Parser parser = new Parser("return null+42;"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 0; ]", stop.toString()); } @Test - public void testEmpty() { + public void testAntiDeps7() { Parser parser = new Parser( """ -struct S{}; +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; """); StopNode stop = parser.parse().iterate(); assertEquals("return 0;", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 0)); } @Test - public void testForwardRef0() { + public void testAntiDeps8() { Parser parser = new Parser( """ -struct S1 { S2 s; } -return new S2; +struct S { int f; } +S v = new S; +S t = new S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop21,arg,Phi(Region48,.f,0));", stop.toString()); } @Test - public void testForwardRef1() { + public void testAntiDeps9() { Parser parser = new Parser( """ -struct S1 { S2? s; } -struct S2 { int x; } -return new S1.s=new S2; +struct S { int f; } +S v = new S; +S t = new S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v; """); StopNode stop = parser.parse().iterate(); - assertEquals("return S2;", stop.toString()); + assertEquals("return S;", stop.toString()); } @Test - public void testcheckNull() { + public void testExample2() { Parser parser = new Parser( -""" -struct I {int i;} -struct P {I pi;} -P p1 = new P; -P p2 = new P; -p2.pi = new I; -p2.pi.i = 2; -if (arg) p1 = new P; -return p1.pi.i + 1; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + """ +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); + StopNode stop = parser.parse().iterate(); + //assertEquals("return new S;", stop.toString()); } - } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java new file mode 100644 index 00000000..f4323fe0 --- /dev/null +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -0,0 +1,83 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter14FloatTest { + @Test public void testSubZeroFloat() { + var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); + // Compare strings so +0.0 and -0.0 remain distinct. + assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); + assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + } + + @Test public void testDeadNumericReturns() { + for( String[] test : new String[][] { + {"if(1) return 7; else return 2.5;", "7"}, + {"if(0) return 7; else return 2.5;", "2.5"}, + {"return 7; return 2.5;", "7"}, + {"return 2.5; return 7;", "2.5"} + } ) { + String src = test[0]; + var stop = new Parser(src).parse().iterate(); + assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); + } + } + + + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testFloat() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testSquareRoot() { + Parser parser = new Parser( +""" +flt guess = arg; +while( 1 ) { + flt next = (arg/guess + guess)/2; + if( next == guess ) break; + guess = next; +} +return guess; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,(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)); + } + + @Test + public void testFPOps() { + Parser parser = new Parser( +""" +flt x = arg; +return x+1==x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); + } +} diff --git a/chapter16/README.md b/chapter16/README.md index 663f175a..c55de47f 100644 --- a/chapter16/README.md +++ b/chapter16/README.md @@ -1,25 +1,25 @@ -# Chapter 16: Constructors and Final fields +# Chapter 16: Constructors [Previous: Chapter 15](../chapter15/README.md) | -[Next: Chapter 17](../chapter17/README.md) +[Next: Chapter 17a](../chapter17a/README.md) # Table of Contents 1. [Constructors](#constructors) 2. [Initialization code](#initialization-code) -3. [Final fields](#final-fields) -4. [Multiple Decls](#multiple-declarations-of-the-same-type) -5. [Memory through constructors](#memory-through-constructors) +3. [Multiple Decls](#multiple-declarations-of-the-same-type) +4. [Memory through constructors](#memory-through-constructors) -In this chapter, we add constructors and final fields. +This chapter adds field defaults and constructor blocks. Chapter 17a will add +fixed bindings and read-only access, using these constructors to initialize them. You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter16) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter15...linear-chapter16) it to the previous chapter. ## Constructors -A big problem with the [Chapter 13](../chapter13/README.md) refs is that +A big problem with the [Chapter 12](../chapter12/README.md) refs is that not-null fields always start out `null`. This is fixed in this chapter, where -a *constructor* syntax is required to initialize `final` and not-null fields. +a *constructor* syntax is required to initialize not-null fields. Fields are initialized in three ways: @@ -46,7 +46,7 @@ struct Point { int x=1; int y=1; }; // Point x and y will start as 1, not 0 return new Point { x=3; }.x; // Returns a 3 ``` -Not-null and final fields *must* be initialized before first use and before the +Not-null fields *must* be initialized before first use and before the end of the allocation. They can be initialized in either the declaration or allocation. They do not start with the default value, although the initialization can be to the default. @@ -81,6 +81,8 @@ struct Square { return new Square; ``` +Another example: + ``` struct Buffer { if( arg < 0 || arg > 1000000 ) @@ -91,32 +93,6 @@ return new Buffer; ``` -## Final fields - -Final fields are declared with a `!` before the name. -This decision is revisited in the next chapter. - -``` -int !x = 17; // Final field x -x = 3; // ERROR: cannot re-assign a final field -``` - -``` -struct Person { u8[]? !name; }; -return new Person { name = null; }; // OK: final name is assigned -``` - -``` -struct Point { int !x; int !y; int !z; }; -return new Point { x=arg; y=z; z=x; }; // ERROR: 'Point' is not fully initialized, field 'y' needs to be set in a constructor -``` - -``` -struct Point { int x; int y; int z; }; // Fields not-final, default to 0 -return new Point { x=arg; y=z; z=x; }; // Ok: x=arg, y=0, z=arg -``` - - ### Multiple declarations of the same type Also new this chapter is allowing multiple declarations with the same type, as @@ -127,16 +103,12 @@ int x,y; // Two int variables declared struct Point { int x,y,z; }; // Three fields declared ``` -``` -int !x=3,!y=5; // Two int variables declared, both are final and initialized -``` ## Memory through constructors -The lazy memory partitioning from [Chapter 10b](../chapter10b/README.md) continues +The lazy memory partitioning from [Chapter 11](../chapter11/README.md) continues through constructors. The parser tracks one `$mem` variable alongside its scalar variables. Branches and loops merge that binding with BulkMemPhi; field access -splits out precise MemPhis when needed. The variable records for types and final -fields live in ScopeNode. There is no separate parser table of memory aliases. +splits out precise MemPhis when needed. The variable records for declared types live in ScopeNode. There is no separate parser table of memory aliases. A constructor computes its field values before the allocation. Reads, writes, and loops in its body update the same `$mem` binding as ordinary code. New then diff --git a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java index a7d31584..97b29a37 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java @@ -402,7 +402,7 @@ public class Parser { /** Parse: name [=expr] */ - private Node parseAsgn(Type t, boolean xfinal) { + private Node parseAsgn(Type t) { boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name int old = _lexer._position; String name = requireId(); @@ -411,7 +411,7 @@ public class Parser { // Bare "name" is not allowed if( t==null ) throw errorSyntax("expression"); // Does this declaration need an initializer and not getting one? - Type init = xfinal || t instanceof TypeMemPtr tmp && !tmp._nil + Type init = t instanceof TypeMemPtr tmp && !tmp._nil // Must be initialized in the "new" constructor ? Type.TOP // Else takes the default @@ -430,11 +430,6 @@ public class Parser { ScopeNode.Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + name + "'"); - // TOP fields are for late-initialized fields; these have never - // been written to, and this must be the final write. Other writes - // need to check the final bit. - if( _scope.in(def._idx)._type!=Type.TOP && def._final ) - throw error("Cannot reassign final '"+name+"'"); t = def._type; // Declared field type } @@ -452,20 +447,13 @@ public class Parser { throw error("Type " + e.str() + " is not of declared type " + t.str()); if( isDecl ) { - if( !_scope.define(name,t,xfinal,expr) ) + if( !_scope.define(name,t,false,expr) ) throw error("Redefining name '" + name + "'"); } else _scope.update(name,expr); return expr; } - /** Parse final: [!]asgn - */ - private Node parseFinal(Type t) { - boolean xfinal = t!=null && match("!"); - return parseAsgn(t,xfinal); - } - /** * Parses an expression statement or a declaration statement where type is a struct * @@ -482,13 +470,13 @@ public class Parser { Node n; Type t = type(); if( t != null ) { - // now parse final [, final]* - n = parseFinal(t); + // Parse comma-separated declarations + n = parseAsgn(t); while( match(",") ) - n = parseFinal(t); + n = parseAsgn(t); // Parse "asgn;" which is just "name = expr;" - } else if( ( n = parseAsgn(null,false)) == null ) { + } else if( ( n = parseAsgn(null)) == null ) { // Something else n = parseExpression(); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 75119a75..0b8be64b 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -25,7 +25,7 @@ public class Chapter10Test { // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. private static final String NULLABLE_POINT_SOURCE = """ struct Point { int x; }; - Point?[] !points = new Point?[2]; + Point?[] points = new Point?[2]; points[arg] = new Point { x = 42; }; Point? p = points[1]; """; diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java deleted file mode 100644 index 4281f0a1..00000000 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.evaluator.Evaluator; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter10bTest { - @Test public void testIndependentField() { - var stop = new Parser(""" - struct S { int x; int y; }; - S s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().iterate(); - assertEquals("return 42;",stop.toString()); - assertEquals(42L,Evaluator.evaluate(stop,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var stop = new Parser(""" - struct S { int x; int y; int z; }; - S a = new S; S b = new S; - S p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().iterate(); - int[] phis = {0}; - stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a=3); - assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - - @Test public void testLateSliceWorklist() throws ReflectiveOperationException { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var parser = new Parser(Chapter10Test.NESTED_MEMORY); - var field = IterPeeps.class.getDeclaredField("WORK"); - field.setAccessible(true); - Object work = field.get(null); - field = work.getClass().getDeclaredField("_R"); - field.setAccessible(true); - ((java.util.Random)field.get(work)).setSeed(seed); - var stop = parser.parse().iterate(); - assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); - } - } -} diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 1183da5f..e009c6b6 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -1,554 +1,64 @@ package com.seaofnodes.simple; - +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; - - -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class Chapter11Test { - - // A placeholder test used to rapidly rotate through fuzzer produced issues - @Test - public void testFuzzer() { - Parser parser = new Parser( -""" -while(-----arg) { - while(-arg) { - while(-----arg*---0) { - int N=--false==false<--arg; - while(N<-4==N) { - N=N+N; - } - } - { - int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); - while(kzEl_r7EYz<---1) { - kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; - if(7) continue; - kzEl_r7EYz=false; - if(-kzEl_r7EYz+arg<-3) continue; - else { - while(kzEl_r7EYz) continue; - arg=----3; - kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); - int v2uF_4ba=9; - } - } - } - int pjcp7rv=arg; - { - int m2ya3=--arg-pjcp7rv; - while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { - m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); - while(arg<-arg) { - arg=arg+--arg; - { - while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); - pjcp7rv=11; - int c=pjcp7rv; - if(--107) break; - else break; - } - } - } - pjcp7rv=pjcp7rv; - int aR0Wkua=-pjcp7rv==pjcp7rv; - { - if(-0!=-0) while(-true) break; - else { - while(aR0Wkua) pjcp7rv=-0/pjcp7rv; - pjcp7rv=pjcp7rv; - } + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; }; + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; }; + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } } - if(-arg) { - { - int Isdlt0fr91=0!=arg; - while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { - Isdlt0fr91=Isdlt0fr91+-1!=true; - if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; - int i=---arg<---arg!=--40; - { - int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; - while(jVX43Ln4Dm<--(3-3)) { - jVX43Ln4Dm=jVX43Ln4Dm+-0<25; - i=24<--(jVX43Ln4Dm); - } - } - i=(arg!=----false>=7); - int Te=5>-arg; - break; - } - } - arg=arg!=--true/arg; - { - int UX=arg; - while(UX<-3*arg) { - UX=UX+arg/-1; - arg=-3--(-UX--47=-arg; - if(---12) while(-UX) break; - break; - } - } - continue; - } - if(arg) arg=--0; - else if(1) continue; - int DtTlhjYq=19==arg; - { - arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); - if(---arg) continue; - else { - int Zsl=40; - while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { - Zsl=Zsl+arg; - } - } - if((arg)) if(-DtTlhjYq) { - } - else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; - else { - int lwbF=arg>=0; - while(lwbF<57) { - lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; - arg=----0+0; - while(49) break; - if(true) DtTlhjYq=-----arg; - DtTlhjYq=0!=-----(0); - } - } - arg=---arg!=DtTlhjYq/--0; - DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; - else { - } - DtTlhjYq=-0; - { - while((0)<-arg) DtTlhjYq=-DtTlhjYq; - } - if(30/--(arg)>=0) DtTlhjYq=-0>=arg; - else { - int Y2wcIQJ=--DtTlhjYq*---11; - if(arg==arg) { - Y2wcIQJ=-----Y2wcIQJ; - Y2wcIQJ=Y2wcIQJ<7; - if(DtTlhjYq) arg=arg; - else break; - while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; - } - continue; - } - arg=DtTlhjYq; - arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; - arg=51+---(-true)!=arg!=----1==-DtTlhjYq; - if(11<=arg) if((arg)+79<32+(-arg)) continue; - else if(true==DtTlhjYq) break; - break; - } -} -if(arg!=62==arg) { - while(---arg) { - { - int bhmDacPM=arg/arg; - while(bhmDacPM1)=-arg+-arg; - break; - } - } - int arg=true; - arg=arg<=arg; - { - int KlC=30; - while(KlC<-2) { - KlC=KlC+-1!=25<=-KlC>KlC>----0; - int Ij=arg>=-arg<=---10; - { - int LAbgurVa2R=-arg!=Ij; - while(LAbgurVa2R<-true) { - LAbgurVa2R=LAbgurVa2R+Ij; - KlC=-LAbgurVa2R; - Ij=42*(-Ij>true); - while(Ij) arg=KlC; - KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; - } - } - int a=Ij<=-KlC; - if(Ij) { - int r1W=a!=-false; - } - else { - arg=56>=Ij; - while(54) break; - arg=8>=-(a<--Ij)/a>=-a-KlC; - } - int Yt_T=KlC; - arg=KlC; - } - } - if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; - else if(----arg) continue; - if(-arg-arg) { - arg=-(-arg)*--(-3)<(-7==-arg----arg); - { - int arg=arg; - while(arg<-0>arg==-arg-(-arg>1+11)) { - arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; - } - } - arg=-arg+arg; - arg=--47+-arg<1>arg*(-arg==-arg--0>3); - arg=(----arg)!=arg+--59; - arg=--4+arg==-(arg)-arg; - } - else { - while(--true-----true*-25+-18>arg>=-----(-arg)---13) { - arg=-0+--true=--0))<=-0/-arg; - int L=-arg; - int B4mHcrf=---vzXB; - int oZWGdzZl=-o3ypEsewxM; - int K6tQqa1=11; - while(0!=--L) { - int sdZfTM=vzXB*true+K6tQqa1; - while(sdZfTMfalse); - sdZfTM=vzXB; - } - } - L=false; - while(31) B4mHcrf=arg; - oZWGdzZl=-0<--oZWGdzZl<=--2; - if(K6tQqa1>=20<=46) { - if(-1+-L) if(oZWGdzZl) { - int wmhv=-----0; - } - int arg=0; - o3ypEsewxM=---(oZWGdzZl); - return -oZWGdzZl/0; - } - else if(arg) { - int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; - return -K6tQqa1; - } - else { - if(---0) { - {} - {} - } - return o3ypEsewxM; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); - } - - - @Test - public void testPrimes() { - Parser parser = new Parser( -""" -if( arg < 2 ) return 0; -int primeCount = 1; -int prime = 3; -while( prime <= arg ) { - int isPrime = 1; - // Check for even case, so the next loop need only check odds - if( (prime/2)*2 == prime ) - continue; - // Check odds up to sqrt of prime - int j = 3; - while( j*j <= prime ) { - if( (prime/j)*j == prime ) { - isPrime = 0; - break; - } - j = j + 2; - } - if( isPrime ) - primeCount = primeCount + 1; - prime = prime + 2; -} -return primeCount; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below - assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 - assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 - assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 - assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 - assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 - } - - @Test - public void testAntiDeps1() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 2;", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 0)); - assertEquals(2L, Evaluator.evaluate(stop, 1)); - } - - @Test - public void testAntiDeps2() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -v.f = arg; -S t = new S; -int i = 0; -if (arg) { - if (arg+1) v = t; - i = v.f; -} else { - v.f = 2; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region48,.f,0);", stop.toString()); - } - - @Test - public void testAntiDeps3() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v0 = new S; -S? v1; -if (arg) v1 = new S; -if (v1) { - v0.f = v1.f; -} else { - v0.f = 2; -} -return v0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - - @Test - public void testAntiDeps4() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -v.f = arg; -S t = new S; -int i = v.f; -if (arg+1) arg= 0; -while (arg) v.f = 2; -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return arg;", stop.toString()); - } - - @Test - public void testAntiDeps5() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -while(1) { - while(arg+1) { arg=arg-1; } - if (arg) break; - v.f = 2; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - @Test - public void testAntiDeps6() { - Parser parser = new Parser( -""" -struct s { int v; }; -s ptr=new s; -while( -arg ) - ptr = new s; -while(1) - arg = arg+ptr.v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); - } - - @Test - public void testAntiDeps7() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -S t = new S; -int i = v.f; -while (arg) { - v.f = arg; - arg = arg-1; -} -return i; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testAntiDeps8() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -S t = new S; -while(arg) { - arg=arg-1; - int f = v.f; - v.f = 2; - if (arg) arg = f; - else v.f = 3; -} -return arg; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop23,arg,Phi(Region50,.f,0));", stop.toString()); - } - - @Test - public void testAntiDeps9() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -S t = new S; -if (arg) { - v.f=2; - int i=t.f; - v.f=i; -} -return v; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return S;", stop.toString()); - } - - @Test - public void testExample2() { - Parser parser = new Parser( -""" -struct S { int f; }; -S v = new S; -int i = arg; -while (arg > 0) { - int j = i/3; - if (arg == 5) - v.f = j; - arg = arg - 1; -} -return v; - """); - StopNode stop = parser.parse().iterate(); - //assertEquals("return new S;", stop.toString()); - } - - @Test - public void testScheduleUse() { - Parser parser = new Parser( -""" -int v0=0; -while(0>=0) { - u1 v1=0; - v1=v0; - if(v1*0) - v0=-v1; -} -return 0; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - - @Test - public void testLoopCarriedDep() { - Parser parser = new Parser( -""" -u32 v0=0; -{ - int v1=0; - while(v1) { - v1=1>>>v0!=0; - v0=v1/0; - } -} -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - } - } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 998053ec..22976058 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -2,82 +2,195 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter12Test { - @Test public void testSubZeroFloat() { - var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); - // Compare strings so +0.0 and -0.0 remain distinct. - assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); - assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); } - @Test public void testDeadNumericReturns() { - for( String[] test : new String[][] { - {"if(1) return 7; else return 2.5;", "7"}, - {"if(0) return 7; else return 2.5;", "2.5"}, - {"return 7; return 2.5;", "7"}, - {"return 2.5; return 7;", "2.5"} - } ) { - String src = test[0]; - var stop = new Parser(src).parse().iterate(); - assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); - } + @Test + public void testLinkedList0() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; }; +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +return head.next.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; }; +LLI? head = null; +while( arg ) { + LLI x = new LLI; + x.next = head; + x.i = arg; + head = x; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( next==null ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 3)); + } @Test - public void testJig() { + public void testCoRecur() { Parser parser = new Parser( """ -return 3.14; +struct int0 { int i; flt0? f; }; +struct flt0 { flt f; int0? i; }; +int0 i0 = new int0; +i0.i = 17; +flt0 f0 = new flt0; +f0.f = 3.14; +i0.f = f0; +f0.i = i0; +return f0.i.f.i.i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return 17;", stop.toString()); } @Test - public void testFloat() { + public void testNullRef0() { Parser parser = new Parser( """ -return 3.14; +struct N { N? next; int i; }; +N n = new N; +return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("return null;", stop.toString()); } @Test - public void testSquareRoot() { + public void testNullRef1() { Parser parser = new Parser( """ -flt guess = arg; -while( 1 ) { - flt next = (arg/guess + guess)/2; - if( next == guess ) break; - guess = next; -} -return guess; +struct M { int m; }; +struct N { M next; int i; }; +N n = new N { next = new M; }; +return n.next; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return M;", stop.toString()); + } + + @Test + public void testNullRef2() { + Parser parser = new Parser( +""" +struct M { int m; }; +struct N { M next; int i; }; +N n = new N { next = null; } +return n.next; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Type null is not of declared type *M",e.getMessage()); } + } + + @Test + public void testNullRef3() { + Parser parser = new Parser( +""" +struct N { N? next; int i; }; +N n = new N; +n.i = 3.14; +return n.i; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + } + + @Test + public void testNullRef4() { + Parser parser = new Parser("-null-5/null-5"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test public void testNullRef5() { + Parser parser = new Parser("return null+42;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + } + + @Test + public void testEmpty() { + Parser parser = new Parser( +""" +struct S{}; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,(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)); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testForwardRef0() { + Parser parser = new Parser( +""" +struct S1 { S2 s; }; +return new S2; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @Test - public void testFPOps() { + public void testForwardRef1() { Parser parser = new Parser( """ -flt x = arg; -return x+1==x; +struct S1 { S2? s; }; +struct S2 { int x; }; +return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 1)); + assertEquals("return S2;", stop.toString()); + } + + @Test + public void testcheckNull() { + Parser parser = new Parser( +""" +struct I {int i;}; +struct P { I? pi; }; +P p1 = new P; +P p2 = new P; +p2.pi = new I; +p2.pi.i = 2; +if (arg) p1 = new P; +return p1.pi.i + 1; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } + } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java index aa496030..c749aba6 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,196 +1,554 @@ package com.seaofnodes.simple; + import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; + + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter13Test { + + // A placeholder test used to rapidly rotate through fuzzer produced issues @Test - public void testJig() { + public void testFuzzer() { Parser parser = new Parser( """ -return 3.14; +while(-----arg) { + while(-arg) { + while(-----arg*---0) { + int N=--false==false<--arg; + while(N<-4==N) { + N=N+N; + } + } + { + int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); + while(kzEl_r7EYz<---1) { + kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; + if(7) continue; + kzEl_r7EYz=false; + if(-kzEl_r7EYz+arg<-3) continue; + else { + while(kzEl_r7EYz) continue; + arg=----3; + kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); + int v2uF_4ba=9; + } + } + } + int pjcp7rv=arg; + { + int m2ya3=--arg-pjcp7rv; + while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { + m2ya3=m2ya3+-(--0<=false=-pjcp7rv>=arg<=arg); + while(arg<-arg) { + arg=arg+--arg; + { + while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); + pjcp7rv=11; + int c=pjcp7rv; + if(--107) break; + else break; + } + } + } + pjcp7rv=pjcp7rv; + int aR0Wkua=-pjcp7rv==pjcp7rv; + { + if(-0!=-0) while(-true) break; + else { + while(aR0Wkua) pjcp7rv=-0/pjcp7rv; + pjcp7rv=pjcp7rv; + } + } + } + if(-arg) { + { + int Isdlt0fr91=0!=arg; + while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { + Isdlt0fr91=Isdlt0fr91+-1!=true; + if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; + int i=---arg<---arg!=--40; + { + int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; + while(jVX43Ln4Dm<--(3-3)) { + jVX43Ln4Dm=jVX43Ln4Dm+-0<25; + i=24<--(jVX43Ln4Dm); + } + } + i=(arg!=----false>=7); + int Te=5>-arg; + break; + } + } + arg=arg!=--true/arg; + { + int UX=arg; + while(UX<-3*arg) { + UX=UX+arg/-1; + arg=-3--(-UX--47=-arg; + if(---12) while(-UX) break; + break; + } + } + continue; + } + if(arg) arg=--0; + else if(1) continue; + int DtTlhjYq=19==arg; + { + arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); + if(---arg) continue; + else { + int Zsl=40; + while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { + Zsl=Zsl+arg; + } + } + if((arg)) if(-DtTlhjYq) { + } + else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; + else { + int lwbF=arg>=0; + while(lwbF<57) { + lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; + arg=----0+0; + while(49) break; + if(true) DtTlhjYq=-----arg; + DtTlhjYq=0!=-----(0); + } + } + arg=---arg!=DtTlhjYq/--0; + DtTlhjYq=-DtTlhjYq/(--argDtTlhjYq; + else { + } + DtTlhjYq=-0; + { + while((0)<-arg) DtTlhjYq=-DtTlhjYq; + } + if(30/--(arg)>=0) DtTlhjYq=-0>=arg; + else { + int Y2wcIQJ=--DtTlhjYq*---11; + if(arg==arg) { + Y2wcIQJ=-----Y2wcIQJ; + Y2wcIQJ=Y2wcIQJ<7; + if(DtTlhjYq) arg=arg; + else break; + while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; + } + continue; + } + arg=DtTlhjYq; + arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; + arg=51+---(-true)!=arg!=----1==-DtTlhjYq; + if(11<=arg) if((arg)+79<32+(-arg)) continue; + else if(true==DtTlhjYq) break; + break; + } +} +if(arg!=62==arg) { + while(---arg) { + { + int bhmDacPM=arg/arg; + while(bhmDacPM1)=-arg+-arg; + break; + } + } + int arg=true; + arg=arg<=arg; + { + int KlC=30; + while(KlC<-2) { + KlC=KlC+-1!=25<=-KlC>KlC>----0; + int Ij=arg>=-arg<=---10; + { + int LAbgurVa2R=-arg!=Ij; + while(LAbgurVa2R<-true) { + LAbgurVa2R=LAbgurVa2R+Ij; + KlC=-LAbgurVa2R; + Ij=42*(-Ij>true); + while(Ij) arg=KlC; + KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; + } + } + int a=Ij<=-KlC; + if(Ij) { + int r1W=a!=-false; + } + else { + arg=56>=Ij; + while(54) break; + arg=8>=-(a<--Ij)/a>=-a-KlC; + } + int Yt_T=KlC; + arg=KlC; + } + } + if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; + else if(----arg) continue; + if(-arg-arg) { + arg=-(-arg)*--(-3)<(-7==-arg----arg); + { + int arg=arg; + while(arg<-0>arg==-arg-(-arg>1+11)) { + arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; + } + } + arg=-arg+arg; + arg=--47+-arg<1>arg*(-arg==-arg--0>3); + arg=(----arg)!=arg+--59; + arg=--4+arg==-(arg)-arg; + } + else { + while(--true-----true*-25+-18>arg>=-----(-arg)---13) { + arg=-0+--true=--0))<=-0/-arg; + int L=-arg; + int B4mHcrf=---vzXB; + int oZWGdzZl=-o3ypEsewxM; + int K6tQqa1=11; + while(0!=--L) { + int sdZfTM=vzXB*true+K6tQqa1; + while(sdZfTMfalse); + sdZfTM=vzXB; + } + } + L=false; + while(31) B4mHcrf=arg; + oZWGdzZl=-0<--oZWGdzZl<=--2; + if(K6tQqa1>=20<=46) { + if(-1+-L) if(oZWGdzZl) { + int wmhv=-----0; + } + int arg=0; + o3ypEsewxM=---(oZWGdzZl); + return -oZWGdzZl/0; + } + else if(arg) { + int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; + return -K6tQqa1; + } + else { + if(---0) { + {} + {} + } + return o3ypEsewxM; + } +} """); StopNode stop = parser.parse().iterate(); - assertEquals("return 3.14;", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); } + @Test - public void testLinkedList0() { + public void testPrimes() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; }; -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; } -return head.next.i; +return primeCount; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below + assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 + assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 + assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 + assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 + assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 } @Test - public void testLinkedList1() { + public void testAntiDeps1() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; }; -LLI? head = null; -while( arg ) { - LLI x = new LLI; - x.next = head; - x.i = arg; - head = x; - arg = arg-1; -} -if( !head ) return 0; -LLI? next = head.next; -if( next==null ) return 1; -return next.i; +struct S { int f; }; +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); - assertEquals(2L, Evaluator.evaluate(stop, 3)); + assertEquals("return 2;", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 0)); + assertEquals(2L, Evaluator.evaluate(stop, 1)); } @Test - public void testCoRecur() { + public void testAntiDeps2() { Parser parser = new Parser( """ -struct int0 { int i; flt0? f; }; -struct flt0 { flt f; int0? i; }; -int0 i0 = new int0; -i0.i = 17; -flt0 f0 = new flt0; -f0.f = 3.14; -i0.f = f0; -f0.i = i0; -return f0.i.f.i.i; +struct S { int f; }; +S v = new S; +v.f = arg; +S t = new S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 17;", stop.toString()); + assertEquals("return Phi(Region48,.f,0);", stop.toString()); } @Test - public void testNullRef0() { + public void testAntiDeps3() { Parser parser = new Parser( """ -struct N { N? next; int i; }; -N n = new N; -return n.next; +struct S { int f; }; +S v0 = new S; +S? v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return null;", stop.toString()); + assertEquals("return S;", stop.toString()); } + @Test - public void testNullRef1() { + public void testAntiDeps4() { Parser parser = new Parser( """ -struct M { int m; }; -struct N { M next; int i; }; -N n = new N { next = new M; }; -return n.next; +struct S { int f; }; +S v = new S; +v.f = arg; +S t = new S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return M;", stop.toString()); + assertEquals("return arg;", stop.toString()); } @Test - public void testNullRef2() { + public void testAntiDeps5() { Parser parser = new Parser( """ -struct M { int m; }; -struct N { M next; int i; }; -N n = new N { next = null; } -return n.next; +struct S { int f; }; +S v = new S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *M",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return S;", stop.toString()); } @Test - public void testNullRef3() { + public void testAntiDeps6() { Parser parser = new Parser( """ -struct N { N? next; int i; }; -N n = new N; -n.i = 3.14; -return n.i; +struct s { int v; }; +s ptr=new s; +while( -arg ) + ptr = new s; +while(1) + arg = arg+ptr.v; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 0; ]", stop.toString()); } @Test - public void testNullRef4() { - Parser parser = new Parser("-null-5/null-5"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + public void testAntiDeps7() { + Parser parser = new Parser( +""" +struct S { int f; }; +S v = new S; +S t = new S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); } - @Test public void testNullRef5() { - Parser parser = new Parser("return null+42;"); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot 'Add' null",e.getMessage()); } + @Test + public void testAntiDeps8() { + Parser parser = new Parser( +""" +struct S { int f; }; +S v = new S; +S t = new S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop23,arg,Phi(Region50,.f,0));", stop.toString()); } @Test - public void testEmpty() { + public void testAntiDeps9() { Parser parser = new Parser( """ -struct S{}; +struct S { int f; }; +S v = new S; +S t = new S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v; """); StopNode stop = parser.parse().iterate(); - assertEquals("return 0;", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 0)); + assertEquals("return S;", stop.toString()); } @Test - public void testForwardRef0() { + public void testExample2() { Parser parser = new Parser( """ -struct S1 { S2 s; }; -return new S2; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } +struct S { int f; }; +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); + StopNode stop = parser.parse().iterate(); + //assertEquals("return new S;", stop.toString()); } @Test - public void testForwardRef1() { + public void testScheduleUse() { Parser parser = new Parser( """ -struct S1 { S2? s; }; -struct S2 { int x; }; -return new S1.s=new S2; +int v0=0; +while(0>=0) { + u1 v1=0; + v1=v0; + if(v1*0) + v0=-v1; +} +return 0; """); StopNode stop = parser.parse().iterate(); - assertEquals("return S2;", stop.toString()); + assertEquals("return 0;", stop.toString()); } @Test - public void testcheckNull() { + public void testLoopCarriedDep() { Parser parser = new Parser( """ -struct I {int i;}; -struct P { I? pi; }; -P p1 = new P; -P p2 = new P; -p2.pi = new I; -p2.pi.i = 2; -if (arg) p1 = new P; -return p1.pi.i + 1; +u32 v0=0; +{ + int v1=0; + while(v1) { + v1=1>>>v0!=0; + v0=v1/0; + } +} """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); } } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java new file mode 100644 index 00000000..f4323fe0 --- /dev/null +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -0,0 +1,83 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter14FloatTest { + @Test public void testSubZeroFloat() { + var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); + // Compare strings so +0.0 and -0.0 remain distinct. + assertEquals("0.0", Evaluator.evaluate(stop,0).toString()); + assertEquals("-1.0", Evaluator.evaluate(stop,1).toString()); + } + + @Test public void testDeadNumericReturns() { + for( String[] test : new String[][] { + {"if(1) return 7; else return 2.5;", "7"}, + {"if(0) return 7; else return 2.5;", "2.5"}, + {"return 7; return 2.5;", "7"}, + {"return 2.5; return 7;", "2.5"} + } ) { + String src = test[0]; + var stop = new Parser(src).parse().iterate(); + assertEquals(src,test[1],com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0).toString()); + } + } + + + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testFloat() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testSquareRoot() { + Parser parser = new Parser( +""" +flt guess = arg; +while( 1 ) { + flt next = (arg/guess + guess)/2; + if( next == guess ) break; + guess = next; +} +return guess; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop10,(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)); + } + + @Test + public void testFPOps() { + Parser parser = new Parser( +""" +flt x = arg; +return x+1==x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((flt)arg==(ToFloat+1.0));", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); + } +} diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 76fdb31e..0f5a5a16 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -9,9 +9,9 @@ import static org.junit.Assert.fail; public class Chapter16Test { @Test - public void testStructFinal3() { - for( String fields : new String[] { "int[] x;", "int[] !x;" } ) { - String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;"; + public void testRequiredReference() { + for( String fields : new String[] { "int[] x;" } ) { + String src = "struct Point { "+fields+" }; Point p = new Point; p.x=p.x+1; return p;"; try { new Parser(src).parse().iterate(); fail(fields); } catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } } @@ -52,31 +52,9 @@ return x+y; assertEquals(5L, Evaluator.evaluate(stop, 0)); } - @Test - public void testFinal0() { - Parser parser = new Parser( -""" -int !x=2; -x=3; -return x; -"""); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } - } - @Test - public void testFinal1() { - Parser parser = new Parser( -""" -int !x=2, y=3; -if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x -return y; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region19,9,3);", stop.toString()); - assertEquals(3L, Evaluator.evaluate(stop, 0)); - assertEquals(9L, Evaluator.evaluate(stop, 1)); - } + + @Test public void testConstruct0() { @@ -90,17 +68,7 @@ return z.x; assertEquals(3L, Evaluator.evaluate(stop, 0)); } - @Test - public void testConstruct1() { - Parser parser = new Parser(""" -struct X { int !x; }; -X z = new X { x=3; }; -return z.x; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return 3;", stop.toString()); - assertEquals(3L, Evaluator.evaluate(stop, 0)); - } + @Test public void testConstruct2() { @@ -115,17 +83,7 @@ return z.x; } - @Test - public void testStructFinal() { - Parser parser = new Parser(""" -struct Point { int !x, !y; }; -Point p = new Point { x=3; y=4; }; -return p; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Point;", stop.toString()); - assertEquals("Obj{x=3,y=4}", Evaluator.evaluate(stop, 0).toString()); - } + // Same as the Chapter13 test with the same name, but using the new // constructor syntax @@ -187,7 +145,7 @@ return next.i; Parser parser = new Parser( """ struct Square { - flt !side = arg; + flt side = arg; // Newtons approximation to the square root, computed in a constructor. // The actual allocation will copy in this result as the initial // value for 'diag'. diff --git a/chapter17/Makefile b/chapter17/Makefile deleted file mode 100644 index 3f1e53ff..00000000 --- a/chapter17/Makefile +++ /dev/null @@ -1,106 +0,0 @@ -SHELL := /bin/bash -# Shared packaging target; included after chapter source variables below. -.DELETE_ON_ERROR: - -# for printing variable values -# usage: make print-VARIABLE -# > VARIABLE = value_of_variable -print-% : ; @echo $* = $($*) - -# literal space -space := $() $() - -# Decide OS-specific questions -# jar-file seperator -ifeq ($(OS),Windows_NT) - SEP = ; -else - SEP = : -endif -# Find a reasonable ctags. -CTAGS = $(shell which ctags) -# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew -ifeq ($(UNAME),Darwin) - CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags) -endif - -# Fun Args to javac. Mostly limit to java8 source definitions, and fairly -# aggressive lint warnings. -JAVAC_ARGS = -g - -# Source code -SIMPLE := com/seaofnodes/simple -SRC := src/main/java -TST := src/test/java -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)) -include $(firstword $(wildcard ../print/print.mk print/print.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) -# All the libraries -libs = $(wildcard lib/*jar) -jars = $(subst $(space),$(SEP),$(libs)) - - -default_targets := $(main_classes) $(test_classes) -# Optionally add ctags to the default target if a reasonable one was found. -ifneq ($(CTAGS),) -default_targets += tags -endif - -default: $(default_targets) - -# Compile just the out-of-date files -$(main_classes): build/classes/main/%class: $(SRC)/%java $(graph_javas) $(print_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) - -$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) - @echo "compiling " $@ " because " $? - @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test - @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) - -# Base launch line for JVM tests -JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" - -tests: $(default_targets) - @echo "testing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore $(test_cp) com.seaofnodes.simple.FuzzerWrap - -fuzzer: $(default_targets) - @echo "fuzzing " $(test_cp) - @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap - - -.PHONY: clean -clean: - rm -rf build - rm -f TAGS - (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0) - -# Download libs from maven -lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar - -# Jars -lib/junit-4.12.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar) - -lib/hamcrest-core-1.3.jar: - @[ -d lib ] || mkdir -p lib - @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar) - -# Build emacs tags (part of a tasty emacs ide experience) -tags: $(main_javas) $(test_javas) - @rm -f TAGS - @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) - -include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) - -.PHONY: build -build: $(main_classes) diff --git a/chapter17/README.md b/chapter17/README.md deleted file mode 100644 index ea1fef28..00000000 --- a/chapter17/README.md +++ /dev/null @@ -1,186 +0,0 @@ -# Chapter 17: Syntax Sugar - -[Previous: Chapter 16](../chapter16/README.md) | -[Next: Chapter 18](../chapter18/README.md) - - -## Table of Contents - -1. [Pre/Post-increment](#prepost-increment) -2. [Operator assignment](#operator-assignment) -3. [var/val](#var--val) -4. [Mutability](#mutability) -5. [Reference immutability](#reference-variables-with-an-initializer-are-deeply-immutable) -6. [Trinary](#trinary) -7. [For Loops](#for-loops) -8. [Memory effects](#memory-effects) - -## Pre/Post-increment - -Allow `arg++` and `arg--` with the usual meanings; the value is updated and the -expression is the pre-update value. Greedy match is used so `arg---arg` parses -as `(arg--)-arg`. - -Also allows `s.fld++` and `ary[idx]++`, and their `--` variants. - -Allow pre-increment for identifiers only, for now: `--pc`. - - -## Operator assignment - -Along the same lines as post-increment, we now allow `op=` assignment -on local variables with semantics similar to other languages. The supported -operators are `+=`, `-=`, `*=`, `/=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`. -Bitwise and shift assignments require integer operands; `>>=` preserves the sign -while `>>>=` shifts in zeros. The assignment evaluates its right-hand side once, -stores the result narrowed to the variable's type, and yields that stored value. -Assignments associate right-to-left, as in `x |= y <<= 2`. - -`x += y` - - -## var & val - -`var` can be used in the `type` position to indicate a "variable" (mutable -value), whose type will be inferred from the required initializing expression. - -`val` is the same as `var`, except it is a "value" (not mutable). - -The inferred value is the Greatest Lower Bound `glb` type of the peephole type, -which means `var` and `val` will not infer types like `u8` or `f32`, instead -inferring `int` and `flt` respectively. Reference types will always infer as -nullable, so e.g. - -`var s = new S;` - -infers as - -`S? s = new S;` - - -## Mutability - -Typed primitive fields are always mutable. Typed reference fields without an -initializer must be mutable to get a value. Initialized reference variables -are immutable by default and can be made mutable with a leading `!`. Contrast -this with what [Chapter 16](../chapter16/README.md) -did with `!`. `var` and `val` keep their current sense and can be used to make -any field mutable or immutable. Fields are always mutable during construction, -but will become immutable at the end of either constructor. - -`int x; x=3; x++; // OK, primitive so mutable` - -`int x =3; x++; // Ok, primitive so mutable, despite initializer` - -Assume in the next examples this struct exists: -`struct S { int x; };` - -Then: - -`S s; s = new S ; s.x++; // OK, no initializer so s.x is mutable` - -`S s; s = new S{x=3}; s.x++; // OK, no initializer so s.x is mutable` - -`S s = new S ; s.x++; // Error, initializer so s.x is immutable` - -`S s = new S{x=3}; s.x++; // Error, initializer so s.x is immutable` - -Leading `!` makes mutable: - -`S !s = new S; s.x++; // Ok, has '!' so mutable` - -`var` is variable, i.e. mutable: - -`var s = new S; s.x++; // Ok, has var so mutable` - -`val` is a "value": not mutable through this reference, but may be mutable -through other references. - -`val s = new S; s.x++; // Error, val so s.x is immutable` - - -### Reference variables with an initializer are deeply immutable - -Initialized reference variables are deeply immutable, but a mutable version of the same -reference can exist. This works for `val` and normal type declarations: `val s -= new S;` and `S s = new S` both make `s` an immutable reference to a `struct S`. - -```cpp -struct Bar { int x; } -Bar !bar = new Bar; // '!' so bar is mutable -bar.x = 3; // Ok, bar is mutable - -struct Foo { Bar !bar; int y; } -Foo !foo = new Foo; // '!' so foo is mutable -foo.bar = bar; // Ok foo is mutable -foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable - -val xfoo = foo; // Throw away mutability deeply on foo -xfoo.bar.x++; // Error, cannot mutate through xfoo - -print(xfoo.bar.x); // Ok to read through xfoo, prints 4 -foo.bar.x++; // Bumps to 5 -print(xfoo.bar.x); // Ok to read through xfoo, prints 5 -``` - -## Trinary - -Allow `pred ? e_true : e_false`. Also allow `pred ? e_true`, where the false result -is the zero type version of the true result. - - -## For Loops - -Allow C/C++ style `for` loops: - -`for( init; test; next ) body` - -Example: - -```cpp -int sum=0; -for( int i=0; i'| '<'| '>='| '<=') shiftExpression)* - ; - -shiftExpression - : additiveExpression ( '<<' | '>>>' | '>>' ) additiveExpression)* - ; - -additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* - ; - -multiplicativeExpression - : unaryExpression (('*' | '/') unaryExpression)* - ; - -unaryExpression - : ('-') unaryExpression - | '!' unaryExpression - | primaryExpression postAssign - ; - -primaryExpression - : INTEGER_LITERAL - | FLOAT_LITERAL - | '(' expression ')' - | 'true' - | 'false' - | 'null' - | newExpression - | IDENTIFIER - ; - -newExpression - : 'new' IDENTIFIER [ '{' block '}' ] - | 'new' IDENTIFIER '[' expression ']' - ; - -postAssign : postFix [ '=' expression ] | [ '#' ]; - -postFix - : '.' IDENTIFIER postFix - | '[' expression ']' postFix - ; - - -IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - -INTEGER_LITERAL - : [1-9]DIGIT* - | [0] - ; - -NON_DIGIT: [a-zA-Z_]; -DIGIT: [0-9]; -``` diff --git a/chapter17/docs/Makefile b/chapter17/docs/Makefile deleted file mode 100644 index 12659b8f..00000000 --- a/chapter17/docs/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -GV := $(wildcard *.gv) -SVG := $(patsubst %.gv,%.svg,$(GV)) - -.PHONY: all -all: $(SVG) - -%.svg: %.gv - dot -Tsvg $< > $@ - -.PHONY: clean -clean: - @rm -f $(SVG) diff --git a/chapter17/pom.xml b/chapter17/pom.xml deleted file mode 100644 index 13caef71..00000000 --- a/chapter17/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.seaofnodes - simple - 1.0 - - chapter17 - jar - Chapter 17 - - ${project.basedir}/../graph - ${project.basedir}/../print - - diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Ary.java b/chapter17/src/main/java/com/seaofnodes/simple/Ary.java deleted file mode 100644 index 8654d41e..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/Ary.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.seaofnodes.simple; - -import java.lang.StringBuilder; -import java.lang.reflect.Array; -import java.util.*; - -// ArrayList with saner syntax -public class Ary extends AbstractList implements List { - public E[] _es; - public int _len; - - public Ary(E[] es) { this(es,es.length); } - public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; } - @SuppressWarnings("unchecked") - public Ary(Class clazz) { this((E[]) Array.newInstance(clazz, 1),0); } - - /** @return list is empty */ - public boolean isEmpty() { return _len==0; } - /** @return active list length */ - public int len() { return _len; } - public int size() { return len(); } - - /** @param i element index - * @return element being returned; throws if OOB - * @exception AIOOBE if !(0 <= i < _len) - */ - public E get( int i ) { return at(i); } - public E at( int i ) { - range_check(i); - return _es[i]; - } - - /** @return last element */ - public E last( ) { return at(_len-1); } - - /** Add element in amortized constant time - * @param e Element to add at end of list - **/ - public E push( E e ) { - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - return (_es[_len++] = e); - } - /** @return remove and return last element */ - public E pop( ) { - range_check(0); - return _es[--_len]; - } - @Override public E removeLast() { return pop(); } - - - /** Add element in amortized constant time - * @param e Element to add at end of list - * @return true */ - public boolean add( E e ) { - if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - _es[_len++] = e; - return true; - } - - ///** Slow, linear-time, element insert. Preserves order. - // * @param i index to insert at, between 0 and _len inclusive. - // * @param e Element to insert - // */ - //public void insert( int i, E e ) { - // if( i < 0 || i>_len ) - // throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - // if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - // System.arraycopy(_es,i,_es,i+1,(_len++)-i); - // _es[i] = e; - //} - - /** Fast, constant-time, element removal. Does not preserve order - * @param i element to be removed - * @return element removed */ - public E del( int i ) { - range_check(i); - E tmp = _es[i]; - _es[i]=_es[--_len]; - return tmp; - } - - ///** Element removal, using '=='. Does not preserve order. - // * @param e element to be removed - // * @return element removed */ - //public E del( E e ) { - // for( int i=0; i<_len; i++ ) { - // E tmp = _es[i]; - // if( tmp==e ) { - // _es[i]=_es[--_len]; - // return tmp; - // } - // } - // return null; - //} - - /** Slow, linear-time, element removal. Preserves order. - * @param i element to be removed - * @return element removed */ - @Override public E remove( int i ) { - range_check(i); - E e = _es[i]; - System.arraycopy(_es,i+1,_es,i,(--_len)-i); - return e; - } - - /** Remove all elements */ - public void clear( ) { _len=0; } - - /** Set existing element - * @param i element to set - * @param e value to set - * @return old value - * @exception AIOOBE if !(0 <= i < _len) - */ - public E set( int i, E e ) { - range_check(i); - E old = _es[i]; - _es[i] = e; - return old; - } - - public Ary setLen( int len ) { - if( len > _len ) - while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - _len = len; - while( _es.length > (len<<1) ) // Shrink if hugely too large - _es = Arrays.copyOf(_es,_es.length>>1); - Arrays.fill(_es,len,_es.length,null); - return this; - } - - /** @param c Collection to be added */ - public boolean addAll( Collection c ) { - if( c==null ) return false; - for( E e : c ) add(e); - return true; - } - public Ary addAll( Iterable it ) { for( E e : it ) add(e); return this; } - - /** @param es Array to be added */ - public Ary addAll( F[] es ) { - if( es==null || es.length==0 ) return this; - while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(es,0,_es,_len,es.length); - _len += es.length; - return this; - } - - /** @param c Collection to be added */ - public Ary addAll( Ary c ) { - if( c==null || c._len==0 ) return this; - while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); - System.arraycopy(c._es,0,_es,_len,c._len); - _len += c._len; - return this; - } - - /** @return compact array version */ - public E[] asAry() { return Arrays.copyOf(_es,_len); } - - /** Sorts in-place - * @param c Comparator to sort by */ - public void sort_update(Comparator c ) { Arrays.sort(_es, 0, _len, c); } - - /** Find the first matching element using ==, or -1 if none. Note that - * most del calls shuffle the list, so the first element might be random. - * @param e Element to find - * @return index of first matching element, or -1 if none */ - public int find( E e ) { - for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i; - return -1; - } - - - /** @return an iterator */ - @Override public Iterator iterator() { return new Iter(); } - private class Iter implements Iterator { - int _i=0; - @Override public boolean hasNext() { return _i<_len; } - @Override public E next() { return _es[_i++]; } - } - - @Override public String toString() { - StringBuilder sb = new StringBuilder().append('{'); - for( int i=0; i<_len; i++ ) { - if( i>0 ) sb.append(','); - if( _es[i] != null ) sb.append(_es[i].toString()); - } - return sb.append('}').toString(); - } - - private void range_check( int i ) { - if( i < 0 || i>=_len ) - throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - } - - // Note that the hashCode() and equals() are not invariant to changes in the - // underlying array. If the hashCode() is used (e.g., inserting into a - // HashMap) and the then the array changes, the hashCode() will change also. - @Override public boolean equals( Object o ) { - if( this==o ) return true; - if( !(o instanceof Ary ary) ) return false; - if( _len != ary._len ) return false; - if( _es == ary._es ) return true; - for( int i=0; i<_len; i++ ) - if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) ) - return false; - return true; - } - @Override public int hashCode( ) { - int sum=_len; - for( int i=0; i<_len; i++ ) - sum += _es[i]==null ? 0 : _es[i].hashCode(); - return sum; - } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java deleted file mode 100644 index eba99128..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ /dev/null @@ -1,238 +0,0 @@ - package com.seaofnodes.simple; - -import com.seaofnodes.simple.IterPeeps.WorkList; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import java.util.*; - -public abstract class GlobalCodeMotion { - - // Arrange that the existing isCFG() Nodes form a valid CFG. The - // Node.use(0) is always a block tail (either IfNode or head of the - // following block). There are no unreachable infinite loops. - public static void buildCFG( StopNode stop ) { - schedEarly(); - Parser.SCHEDULED = true; - schedLate( stop); - } - - - // ------------------------------------------------------------------------ - // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses - // (except at loops). Since defs are visited first - and hoisted as early - // as possible, when we come to a use we place it just after its deepest - // input. - private static void schedEarly() { - ArrayList rpo = new ArrayList<>(); - BitSet visit = new BitSet(); - _rpo_cfg(Parser.START, visit, rpo); - // Reverse Post-Order on CFG - for( int j=rpo.size()-1; j>=0; j-- ) { - CFGNode cfg = rpo.get(j); - cfg.loopDepth(); - for( Node n : cfg._inputs ) - _schedEarly(n,visit); - // In dead infinite loops, entire code blocks may be unreachable - // from below. Reach down from the CFG to their Phis so their - // inputs are scheduled too. - if( cfg instanceof RegionNode ) - for( Node phi : cfg._outputs ) - if( phi instanceof PhiNode ) - _schedEarly(phi,visit); - } - } - - // Post-Order of CFG - private static void _rpo_cfg(Node n, BitSet visit, ArrayList rpo) { - if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) - return; // Been there, done that - visit.set(cfg._nid); - for( Node use : cfg._outputs ) - _rpo_cfg(use,visit,rpo); - rpo.add(cfg); - } - - private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that - assert !(n instanceof CFGNode); - visit.set(n._nid); - // Schedule inputs first, except Phis: following their backedges would - // enter a data cycle before its control has been scheduled. - for( Node def : n._inputs ) - if( def!=null && !(def instanceof PhiNode) ) - _schedEarly(def,visit); - // An existing edge 0 already supplies control (or a Phi/Proj binding). - if( n.in(0)==null ) { - // Schedule at deepest input - CFGNode early = Parser.START; // Maximally early, lowest idepth - for( int i=1; i early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); // First place this can go - } - } - - // ------------------------------------------------------------------------ - private static void schedLate( StopNode stop) { - CFGNode[] late = new CFGNode[Node.UID()]; - Node[] ns = new Node[Node.UID()]; - // Record Load NIDs at all their CFG block choices, then check against - // Store block choices to force a Load above an anti-dependent Store. - int[] anti = new int[Node.UID()]; - // Breadth-first scheduling - breadth(stop,ns,late,anti); - - // Copy the best placement choice into the control slot - for( int i=0; i work = new WorkList<>(); - work.push(stop); - Node n; - outer: - while( (n = work.pop()) != null ) { - assert late[n._nid]==null; // No double visit - // These I know the late schedule of, and need to set early for loops - if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); - else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); - else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg; - else { - - // All uses done? - for( Node use : n._outputs ) - if( use!=null && late[use._nid]==null ) - continue outer; // Nope, await all uses done - - // Loads need their memory inputs' uses also done - if( n instanceof LoadNode ld ) - for( Node memuse : ld.antiDeps() ) - if( late[memuse._nid]==null ) - continue outer; - - // All uses done, schedule - _doSchedLate(n,ns,late,anti); - } - - // A use just finished; reconsider its inputs and waiting loads, - // even when the shared memory input was already scheduled. - for( Node def : n._inputs ) { - if( def==null ) continue; - if( late[def._nid]==null ) work.push(def); - wakeLoads(def,late,work,new BitSet()); - } - if( n instanceof LoopNode loop ) - for( Node phi : loop._outputs ) - if( phi instanceof PhiNode && late[phi._nid]==null ) - work.push(phi); - } - } - - private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { - // Walk uses, gathering the LCA (Least Common Ancestor) of uses - CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); - assert early != null; - CFGNode lca = null; - for( Node use : n._outputs ) - if( use != null ) - lca = use_block(n,use, late).domLCA(lca,null); - - // Loads may need anti-dependencies, raising their LCA - if( n instanceof LoadNode load ) - lca = find_anti_dep(lca,load,early,late,anti); - - // Walk up from the LCA to the early, looking for best place. This is - // the lowest execution frequency, approximated by least loop depth and - // deepest control flow. - CFGNode best = lca; - lca = lca.idom(); // Already found best for starting LCA - for( ; lca != early.idom(); lca = lca.idom() ) - if( better(lca,best) ) - best = lca; - assert !(best instanceof IfNode); - ns [n._nid] = n; - late[n._nid] = best; - } - - // Block of use. Normally from late[] schedule, except for Phis, which go - // to the matching Region input. - private static CFGNode use_block(Node n, Node use, CFGNode[] late) { - if( !(use instanceof PhiNode phi) ) - return late[use._nid]; - CFGNode found=null; - for( int i=1; i best.idepth() || - best instanceof IfNode; - } - - // An allocation may consume a load's memory through a partial aggregate. - private static void wakeLoads(Node def, CFGNode[] late, WorkList work, BitSet visit) { - if( visit.get(def._nid) ) return; - visit.set(def._nid); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); - if( def instanceof MemMergeNode ) - for( int i=1; iearly, flagging Load's block location choices - for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) - anti[cfg._nid] = load._nid; - // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.antiDeps() ) { - switch( mem ) { - case StoreNode st: - assert late[st._nid]!=null; - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case NewNode st: - assert late[st._nid]!=null; - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); - break; - case PhiNode phi: - // Repeat anti-dep for matching Phi inputs. - // No anti-dep edges but may raise the LCA. - for( int i=1; i { - private static final com.seaofnodes.print.IRPrinter PRINT = - new com.seaofnodes.print.IRPrinter<>(new IRPrinter()); - @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } - @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } - @Override public Kind kind(Node n) { - if( n instanceof StartNode ) return Kind.START; - if( n instanceof StopNode ) return Kind.STOP; - if( n instanceof LoopNode ) return Kind.LOOP; - if( n instanceof RegionNode ) return Kind.REGION; - if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof ReturnNode ) return Kind.RETURN; - if( n instanceof ConstantNode ) return Kind.CONSTANT; - if( n instanceof CProjNode ) return Kind.CPROJ; - if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ; - if( n instanceof CFGNode ) return Kind.CTRL; - if( n instanceof MultiNode ) return Kind.MULTI; - return Kind.DATA; - } - @Override public int index(Node n) { - if( n instanceof ProjNode p ) return p._idx; - if( n instanceof CProjNode p ) return p._idx; - return n._nid; - } - - public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java deleted file mode 100644 index 5a80698a..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; - -import java.util.Arrays; -import java.util.BitSet; -import java.util.Random; - -/** - * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point - * so that no more peepholes apply. This should be linear because peepholes rarely - * (never?) increase code size. The graph should monotonically reduce in some - * dimension, which is usually size. It might also reduce in e.g. number of - * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper - * ones. - *
- * The theoretical overall worklist is mindless just grabbing the next thing and - * doing it. If the graph changes, put the neighbors on the worklist. - *
- * Lather, Rinse, Repeat until the worklist runs dry. - *

- * The main issues we have to deal with: - * - *

    - *
  • Nodes have uses; replacing some set of Nodes with another requires more graph - * reworking. Not rocket science, but it can be fiddly. Its helpful to have a - * small set of graph munging utilities, and the strong invariant that the graph - * is stable and correct between peepholes. In our case `Node.subsume` does - * most of the munging, building on our prior stable Node utilities.
  • - * - *
  • Changing a Node also changes the graph "neighborhood". The neighbors need to - * be checked to see if THEY can also peephole, and so on. After any peephole - * or graph update we put a Nodes uses and defs on the worklist.
  • - * - *
  • Our strong invariant is that for all Nodes, either they are on the worklist - * OR no peephole applies. This invariant is easy to check, although expensive. - * Basically the normal "iterate peepholes to a fixed point" is linear, and this - * check is linear at each peephole step... so quadratic overall. Its a useful - * assert, but one we can disable once the overall algorithm is stable - and - * then turn it back on again when some new set of peepholes is misbehaving. - * The code for this is turned on in `IterPeeps.iterate` as `assert - * progressOnList(stop);`
  • - *
- */ -public abstract class IterPeeps { - - private static final WorkList WORK = new WorkList<>(); - - public static N add( N n ) { - return (N)WORK.push(n); - } - - public static void addAll( Ary ary ) { - WORK.addAll(ary); - } - - /** - * Iterate peepholes to a fixed point - */ - public static StopNode iterate(StopNode stop) { - assert progressOnList(stop); - int cnt=0; - - Node n; - 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() ) { - 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 - if( x != n || !(x instanceof ConstantNode) ) { - // All outputs of n (changing node) not x (prior existing node). - for( Node z : n._outputs ) WORK.push(z); - // Everybody gets a free "go again" in case they didn't get - // made in their final form. - WORK.push(x); - // If the result is not self, revisit all inputs (because - // there's a new user), and replace in the graph. - if( x != n ) { - for( Node z : n. _inputs ) WORK.push(z); - n.subsume(x); - } - } - // If there are distant neighbors, move to worklist - n.moveDepsToWorklist(); - 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); - } - - return stop; - } - - // Visit ALL nodes and confirm the invariant: - // Either you are on the WORK worklist OR running `iter()` makes no progress. - - // This invariant ensures that no progress is missed, i.e., when the - // worklist is empty we have indeed done all that can be done. To help - // with debugging, the {@code assert} is broken out in a place where it is easy to - // stop if a change is found. - - // Also, the normal usage of `iter()` may attempt peepholes with distance - // neighbors and these should fail, but will then try to add dependencies - // {@link #Node.addDep} which is a side effect in an assert. The {@link - // #midAssert} is used to stop this side effect. - private static boolean MID_ASSERT; - public static boolean midAssert() { return MID_ASSERT; } - private static boolean progressOnList(Node stop) { - MID_ASSERT = true; - int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; - Node changed = stop.walk( n -> { - Node m = n; - if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=5) ) { // Types must be forwards, even if on worklist - if( WORK.on(n) ) return null; - m = n.peepholeOpt(); - if( m==null ) return null; - } - System.err.println("BREAK HERE FOR BUG"); - return m; - }); - Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop; - MID_ASSERT = false; - return changed==null; - } - - public static void reset() { - WORK.clear(); - } - - /** - * Classic WorkList, with a fast add/remove, dup removal, random pull. - * The Node's nid is used to check membership in the worklist. - */ - public static class WorkList { - - private Node[] _es; - private int _len; - private final BitSet _on; // Bit set if Node._nid is on WorkList - private final Random _R; // For randomizing pull from the WorkList - private final long _seed; - - /* Useful stat - how many nodes are processed in the post parse iterative opt */ - private long _totalWork = 0; - - WorkList() { this(123); } - WorkList(long seed) { - _es = new Node[1]; - _len=0; - _on = new BitSet(); - _seed = seed; - _R = new Random(); - _R.setSeed(_seed); - } - - /** - * Pushes a Node on the WorkList, ensuring no duplicates - * If Node is null it will not be added. - */ - public E push( E x ) { - if( x==null ) return null; - int idx = x._nid; - if( !_on.get(idx) ) { - _on.set(idx); - if( _len==_es.length ) - _es = Arrays.copyOf(_es,_len<<1); - _es[_len++] = x; - _totalWork++; - } - return x; - } - - public void addAll( Ary ary ) { - for( E n : ary ) - push(n); - } - - /** - * True if Node is on the WorkList - */ - boolean on( E x ) { return _on.get(x._nid); } - - /** - * Removes a random Node from the WorkList; null if WorkList is empty - */ - E pop() { - if( _len == 0 ) return null; - int idx = _R.nextInt(_len); - E x = (E)_es[idx]; - _es[idx] = _es[--_len]; // Compress array - _on.clear(x._nid); - return x; - } - - public void clear() { - _len = 0; - _on.clear(); - _R.setSeed(_seed); - _totalWork = 0; - } - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 9f81d556..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,1427 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.graph.GraphObserver; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; - -import java.util.*; - -/** - * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract - * Syntax Tree structure. - *

- * This is a simple recursive descent parser. All lexical analysis is done here as well. - */ -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 - * through the entire parser and optimizer. - *

- * To make the compiler multithreaded, this field will have to move into a TLS. - */ - public static StartNode START; - - public static ConstantNode ZERO; // Very common node, cached here - public static XCtrlNode XCTRL; // Very common node, cached here - - // Next available memory alias number - static int ALIAS; - - public StopNode STOP; - - // Debugger Printing. - public static boolean SCHEDULED; // True if debug printer can use schedule info - - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. - private final Lexer _lexer; - - /** - * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time - * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how - * we handle branching. See {@link #parseIf()}. - *

- * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds - * variable names to Nodes. The top of this stack represents current scope. - *

- * We keep a list of all ScopeNodes so that we can show them in graphs. - * @see #parseIf() - * @see #_xScopes - */ - public ScopeNode _scope; - - /** - * List of keywords disallowed as identifiers - */ - private static final HashSet KEYWORDS = new HashSet<>(){{ - add("bool"); - add("break"); - add("byte"); - add("continue"); - add("else"); - add("f32"); - add("f64"); - add("false"); - add("flt"); - add("i16"); - add("i32"); - add("i64"); - add("i8"); - add("if"); - add("int"); - add("new"); - add("null"); - add("return"); - add("struct"); - add("true"); - add("u1"); - add("u16"); - add("u32"); - add("u8"); - add("while"); - }}; - - - /** - * We clone ScopeNodes when control flows branch; it is useful to have - * a list of all active ScopeNodes for purposes of visualization of the SoN graph - */ - public final Stack _xScopes = new Stack<>(); - - ScopeNode _continueScope; - ScopeNode _breakScope; // Merge all the while-breaks here - - // Mapping from a type name to a Type. The string name matches - // `type.str()` call. No TypeMemPtrs are in here, because Simple does not - // have C-style '*ptr' references. - public static HashMap TYPES; - - // Mapping from a type name to the constructor for a Type. - public final HashMap INITS; - - - public Parser(String source, TypeInteger arg) { - PARSER = this; - Node.reset(); - IterPeeps.reset(); - SCHEDULED = false; - _lexer = new Lexer(source); - _scope = new ScopeNode(); - _continueScope = _breakScope = null; - START = new StartNode(arg); - STOP = new StopNode(source); - ZERO = con(0).keep(); - XCTRL= new XCtrlNode().peephole().keep(); - ALIAS = 2; // alias 0 for the control, 1 for memory - TYPES = defaultTypes(); - INITS = new HashMap<>(); - } - - public Parser(String source) { - this(source, TypeInteger.BOT); - } - - @Override - public String toString() { return _lexer.toString(); } - - public static HashMap defaultTypes() { - return new HashMap<>() {{ - put("bool",TypeInteger.U1 ); - put("byte",TypeInteger.U8 ); - put("f32" ,TypeFloat .B32); - put("f64" ,TypeFloat .BOT); - put("flt" ,TypeFloat .BOT); - put("i16" ,TypeInteger.I16); - put("i32" ,TypeInteger.I32); - put("i64" ,TypeInteger.BOT); - put("i8" ,TypeInteger.I8 ); - put("int" ,TypeInteger.BOT); - put("u1" ,TypeInteger.U1 ); - put("u16" ,TypeInteger.U16); - put("u32" ,TypeInteger.U32); - put("u8" ,TypeInteger.U8 ); - put("val" ,Type.TOP); // Marker type, indicates type inference - put("var" ,Type.BOTTOM); // Marker type, indicates type inference - }}; - } - - // Debugging utility to find a Node by index - public static Node find(int nid) { return START.find(nid); } - - private Node ctrl() { return _scope.ctrl(); } - private N ctrl(N n) { return _scope.ctrl(n); } - - public StopNode parse() { - _xScopes.push(_scope); - // Enter a new scope for the initial control and arguments - _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, new ProjNode(START, 1, ScopeNode.MEM0).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); - - // Parse whole program - parseBlock(false); - - if( ctrl()._type==Type.CONTROL ) - STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); - _scope.kill(); - _xScopes.pop(); - for( StructNode init : INITS.values() ) - init.unkeep().kill(); - INITS.clear(); - if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); - STOP.peephole(); - return STOP; - } - - /** - * Parses a block - * - *

-     *     '{' statements '}'
-     * 
- * Does not parse the opening or closing '{}' - * @return a {@link Node} or {@code null} - */ - private Node parseBlock(boolean inCon) { - // Enter a new scope - _scope.push(inCon); - while (!peek('}') && !_lexer.isEOF()) - parseStatement(); - // Exit scope - _scope.pop(); - return null; - } - - /** - * Parses a statement - * - *
-     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
-     * 
- * @return a {@link Node} or {@code null} - */ - private Node parseStatement() { - if( false ) return null; - else if (matchx("return") ) return parseReturn(); - else if (match ("{") ) return require(parseBlock(false),"}"); - else if (matchx("if") ) return parseIf(); - else if (matchx("while") ) return parseWhile(); - else if (matchx("for") ) return parseFor(); - else if (matchx("break") ) return parseBreak(); - else if (matchx("continue")) return parseContinue(); - else if (matchx("struct") ) return parseStruct(); - else if (matchx(";") ) return null; // Empty statement - // Declaration or normal assignment/expression - else return parseDeclarationStatement(); - } - - /** - * Parses a while statement - * - *
-     *     while ( expression ) statement
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseWhile() { - require("("); - return parseLooping(false); - } - - - /** - * Parses a for statement - * - *
-     *     for( var x=init; test; incr ) body
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseFor() { - // { var x=init,y=init,...; - // while( pred ) { - // body; - // next; - // } - // } - require("("); - _scope.push(); // Scope for the index variables - if( !match(";") ) // Can be empty init "for(;test;next) body" - parseDeclarationStatement(); // Non-empty init - Node rez = parseLooping(true); - _scope.pop(); // Exit index variable scope - return rez; - } - - // Shared by `for` and `while` - private Node parseLooping( boolean doFor ) { - - var savedContinueScope = _continueScope; - var savedBreakScope = _breakScope; - - // Loop region has two control inputs, the first is the entry - // point, and second is back edge that is set after loop is parsed - // (see end_loop() call below). Note that the absence of back edge is - // used as an indicator to switch off peepholes of the region and - // associated phis; see {@code inProgress()}. - - ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here - - // At loop head, we clone the current Scope (this includes all - // names in every nesting level within the Scope). - // We create phis eagerly for all the names we find, see dup(). - - // Save the current scope as the loop head - ScopeNode head = _scope.keep(); - // Clone the head Scope to create a new Scope for the body. - // Create phis eagerly as part of cloning - _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis - - // Parse predicate - var pred = peek(';') ? con(1) : parseAsgn(); - require( doFor ? ";" : ")" ); - - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); - // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - - // for( ;;next ) body - int nextPos = -1, nextEnd = -1; - if( doFor ) { - // Skip the next expression and parse it later - nextPos = pos(); - skipAsgn(); - nextEnd = pos(); - require(")"); - } - - // Clone the body Scope to create the break/exit Scope which accounts for any - // side effects in the predicate. The break/exit Scope will be the final - // scope after the loop, and its control input is the False branch of - // the loop predicate. Note that body Scope is still our current scope. - ctrl(ifF); - _xScopes.push(_breakScope = _scope.dup()); - _breakScope.addGuards(ifF,pred,true); // Up-cast predicate - - // No continues yet - _continueScope = null; - - // Parse the true side, which corresponds to loop body - // Our current scope is the body Scope - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate - parseStatement(); // Parse loop body - _scope.removeGuards(ifT); - - // Merge the loop bottom into other continue statements - if (_continueScope != null) { - _continueScope = jumpTo(_continueScope); - _scope.kill(); - _scope = _continueScope; - } - - // Now append the next code onto the body code - if( doFor ) { - int old = pos(nextPos); - if( !peek(')') ) - parseAsgn(); - if( pos() != nextEnd ) - throw errorSyntax( "Unexpected code after expression" ); - pos(old); - } - - // The true branch loops back, so whatever is current _scope.ctrl gets - // added to head loop as input. endLoop() updates the head scope, and - // goes through all the phis that were created earlier. For each phi, - // it sets the second input to the corresponding input from the back - // edge. If the phi is redundant, it is replaced by its sole input. - var exit = _breakScope; - head.endLoop(_scope, exit); - head.unkeep().kill(); - - _xScopes.pop(); // Cleanup - _xScopes.pop(); // Cleanup - - _continueScope = savedContinueScope; - _breakScope = savedBreakScope; - - // At exit the false control is the current control, and - // the scope is the exit scope after the exit test. - _xScopes.pop(); - _xScopes.push(exit); - _scope = exit; - return _scope; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope - // Prune nested lexical scopes that have depth > than the loop head - // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur._lexSize.size() > _breakScope._lexSize.size() ) - cur.pop(); - // If this is a continue then first time the target is null - // So we just use the pruned current scope as the base for the - // "continue" - if( toScope == null ) - return cur; - // toScope is either the break scope, or a scope that was created here - assert toScope._lexSize.size() <= _breakScope._lexSize.size(); - toScope.ctrl(toScope.mergeScopes(cur).peephole()); - return toScope; - } - - private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } - - private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } - private Node parseBreak () { - checkLoopActive(); - // At the time of the break, and loop-exit conditions are only valid if - // they are ALSO valid at the break. It is the intersection of - // conditions here, not the union. - _breakScope.removeGuards(_breakScope.ctrl()); - _breakScope = require(jumpTo(_breakScope ),";"); - _breakScope.addGuards(_breakScope.ctrl(), null, false); - return _breakScope; - } - - // Look for an unbalanced `)`, skipping balanced - private Type skipAsgn() { - int paren=0; - while( true ) - // Next X char handles skipping complex comments - switch( _lexer.nextXChar() ) { - case Character.MAX_VALUE: - throw Utils.TODO(); - case ')': - if( --paren<0 ) - return posT(pos()-1); // Leave the `)` behind - break; - case '(': paren ++; break; - default: break; - } - } - - - /** - * Parses a statement - * - *
-     *     if ( expression ) statement [else statement]
-     * 
- * @return a {@link Node}, never {@code null} - */ - private Node parseIf() { - // Parse predicate - require("("); - var pred = require(parseAsgn(), ")"); - return parseTrinary(pred,true,"else"); - } - - // Parse a conditional expression, merging results. - private Node parseTrinary( Node pred, boolean stmt, String fside ) { - pred.keep(); - - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); - // In if true branch, the ifT proj node becomes the ctrl - // But first clone the scope and set it as current - int ndefs = _scope.nIns(); - ScopeNode fScope = _scope.dup(); // Duplicate current scope - fScope.ctrl(ifF); // The pending arm already starts at False - _xScopes.push(fScope); // For graph visualization we need all scopes - - // Parse the true side - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred,false); // Up-cast predicate - Node lhs = stmt ? parseStatement() : parseAsgn().keep(); // Parse true-side - _scope.removeGuards(ifT); - - ScopeNode tScope = _scope; - - // Parse the false side - _scope = fScope; // Restore scope, then parse else block if any - ifF.unkeep(); // fScope already owns the false control - // Up-cast predicate, even if not else clause, because predicate can - // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` - _scope.addGuards(ifF,pred,true); - boolean doRHS = match(fside); - Node rhs = doRHS - ? (stmt ? parseStatement() : parseAsgn()) - : (stmt ? null : con(lhs._type.makeZero())); - _scope.removeGuards(ifF); - if( doRHS ) - fScope = _scope; - pred.unkeep(); - - // Check for `if(pred) int x=17;` - if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) - throw error("Cannot define a new name on one arm of an if"); - - // Check the trinary widening int/flt - if( !stmt ) { - rhs = widenInt( rhs, lhs._type ).keep(); - lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); - rhs.unkeep(); - } - - // Merge results - _scope = tScope; - _xScopes.pop(); // Discard pushed from graph display - - RegionNode r = ctrl(tScope.mergeScopes(fScope)); - Node ret = stmt ? r : peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs)); - r.peephole(); - return ret; - } - - /** - * Parses a return statement; "return" already parsed. - * The $ctrl edge is killed. - * - *
-     *     'return' expr ;
-     * 
- * @return an expression {@link Node}, never {@code null} - */ - private Node parseReturn() { - var expr = require(parseAsgn(), ";"); - Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); - ctrl(XCTRL); // Kill control - return ret; - } - - - - /** Parse: [name '='] expr - */ - private Node parseAsgn() { - int old = pos(); - String name = _lexer.matchId(); - // Just a plain expression, no assignment. - // Distinguish `var==expr` from `var=expr` - if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) - { pos(old); return parseExpression(); } - - // Parse assignment expression - Node expr = parseAsgn(); - - // Final variable to update - ScopeNode.Var def = _scope.lookup(name); - if( def==null ) - throw error("Undefined name '" + name + "'"); - - // TOP fields are for late-initialized fields; these have never - // been written to, and this must be the final write. Other writes - // outside the constructor need to check the final bit. - if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.inCon() ) - throw error("Cannot reassign final '"+name+"'"); - - // Lift expression, based on type - Node lift = liftExpr(expr, def.type(), def._final); - // Update - _scope.update(name,lift); - // Return un-lifted expr - return expr; - } - - // Make finals deep; widen ints to floats; narrow wide int types. - // Early error if types do not match variable. - private Node liftExpr( Node expr, Type t, boolean xfinal ) { - assert !(expr._type instanceof TypeMemPtr tmp) || !tmp.isFRef(); - // Final is deep on ptrs - if( xfinal && t instanceof TypeMemPtr tmp ) { - t = tmp.makeRO(); - expr = peep(new ReadOnlyNode(expr)); - } - // Auto-widen int to float - expr = widenInt( expr, t ); - // Auto-narrow wide ints to narrow ints - expr = zsMask(expr,t); - // Type is sane - if( !expr._type.isa(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - return expr; - } - - private Node widenInt( Node expr, Type t ) { - return expr._type instanceof TypeInteger && t instanceof TypeFloat - ? peep(new ToFloatNode(expr)) : expr; - } - - /** - * Parse declaration or expression statement - * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';' - *

- * exprAsgn = var '=' exprAsgn | expr - */ - private Node parseDeclarationStatement() { - Type t = type(); - if( t == null ) - return require(parseAsgn(),";"); - - // now parse var['=' asgnexpr] in a loop - Node n = parseDeclaration(t); - while( match(",") ) - n = parseDeclaration(t); - return require(n,";"); - } - - /** Parse final: [!]var['=' asgn] - */ - private Node parseDeclaration(Type t) { - assert t!=null; - // Has var/val instead of a user-declared type - boolean inferType = t==Type.TOP || t==Type.BOTTOM; - boolean hasBang = match("!"); - String name = requireId(); - - // Optional initializing expression follows - boolean xfinal = false; - Node expr; - if( match("=") ) { - expr = parseAsgn(); - // `val` is always final - xfinal = (t==Type.TOP) || - // var is always not-final, final if no Bang AND TMP since primitives are not-final by default - (t!=Type.BOTTOM && !hasBang && (t instanceof TypeMemPtr)); - // var/val, then type comes from expression - if( inferType ) - t = expr._type.glb(); - - } else { - if( inferType && !_scope.inCon() ) - throw errorSyntax("=expression"); - expr = con(t.makeInit()); - } - - // Lift expression, based on type - Node lift = liftExpr(expr, t, xfinal); - if( xfinal && t instanceof TypeMemPtr tmp ) - t = tmp.makeRO(); - - // Define a new name, - if( !_scope.define(name,t,xfinal,lift) ) - throw error("Redefining name '" + name + "'"); - return null; - } - - - - /** - * Parse a struct declaration, and return the following statement. - * Only allowed in top level scope. - * Structs cannot be redefined. - * - * @return The statement following the struct - */ - private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); - String typeName = requireId(); - Type t = TYPES.get(typeName); - if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) ) - throw errorSyntax("struct '" + typeName + "' cannot be redefined"); - - // A Block scope parse, and inspect the scope afterward for fields. - _scope.push(true); - _xScopes.push(_scope); - require("{"); - while (!peek('}') && !_lexer.isEOF()) - parseStatement(); - - // Grab the declarations and build fields and a Struct - int lexlen = _scope._lexSize.last(); - int varlen = _scope._vars._len; - StructNode s = new StructNode(); - Field[] fs = new Field[varlen-lexlen]; - for( int i=lexlen; i - * expr : bitwise [? expr [: expr]] - * - * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpression() { - Node expr = parseBitwise(); - return match("?") ? parseTrinary(expr,false,":") : expr; - } - - /** - * Parse an bitwise expression - * - *

-     *     bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
-     * 
- * @return a bitwise expression {@link Node}, never {@code null} - */ - private Node parseBitwise() { - Node lhs = parseComparison(); - while( true ) { - if( false ) ; - else if( match("&") ) lhs = new AndNode(lhs,null); - else if( match("|") ) lhs = new OrNode(lhs,null); - else if( match("^") ) lhs = new XorNode(lhs,null); - else break; - lhs.setDef(2,parseComparison()); - lhs = peep(lhs); - } - return lhs; - } - - /** - * Parse an expression of the form: - * - *
-     *     expr : additiveExpr op additiveExpr
-     * 
- * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - var lhs = parseShift(); - while( true ) { - int idx=0; boolean negate=false; - // Test for any local nodes made, and "keep" lhs during peepholes - if( false ) ; - else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } - else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } - else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } - else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } - else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } - else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } - else break; - // Peepholes can fire, but lhs is already "hooked", kept alive - lhs.setDef(idx,parseShift()); - lhs = peep(lhs.widen()); - if( negate ) // Extra negate for != - lhs = peep(new NotNode(lhs)); - } - return lhs; - } - - /** - * Parse an additive expression - * - *
-     *     shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
-     * 
- * @return a shift expression {@link Node}, never {@code null} - */ - private Node parseShift() { - Node lhs = parseAddition(); - while( true ) { - if( false ) ; - else if( match("<<") ) lhs = new ShlNode(lhs,null); - else if( match(">>>")) lhs = new ShrNode(lhs,null); - else if( match(">>") ) lhs = new SarNode(lhs,null); - else break; - lhs.setDef(2,parseAddition()); - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse an additive expression - * - *
-     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
-     * 
- * @return an add expression {@link Node}, never {@code null} - */ - private Node parseAddition() { - Node lhs = parseMultiplication(); - while( true ) { - if( false ) ; - else if( match("+") ) lhs = new AddNode(lhs,null); - else if( match("-") ) lhs = new SubNode(lhs,null); - else break; - lhs.setDef(2,parseMultiplication()); - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse an multiplicativeExpr expression - * - *
-     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
-     * 
- * @return a multiply expression {@link Node}, never {@code null} - */ - private Node parseMultiplication() { - var lhs = parseUnary(); boolean mul; - while( true ) { - if( false ) ; - else if( match("*") ) lhs = new MulNode(lhs,null); - else if( match("/") ) lhs = new DivNode(lhs,null); - else break; - lhs.setDef(2,parseUnary()); - lhs = peep(lhs.widen()); - } - return lhs; - } - - /** - * Parse a unary minus expression. - * - *
-     *     unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
-     * 
- * @return a unary expression {@link Node}, never {@code null} - */ - private Node parseUnary() { - // Pre-dec/pre-inc - int old = pos(); - if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post - String name = _lexer.matchId(); - if( name!=null ) { - ScopeNode.Var n = _scope.lookup(name); - if( n != null ) { - if( n._final ) - throw error("Cannot reassign final '"+n._name+"'"); - Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); - _scope.update(n,expr); - return expr; - } - } - // Reset, try again - pos(old); - } - if (match("-")) return peep(new MinusNode(parseUnary()).widen()); - if (match("!")) return peep(new NotNode(parseUnary())); - return parsePostfix(parsePrimary()); - } - - /** - * Parse a primary expression: - * - *
-     *     primaryExpr : integerLiteral | true | false | null | new Type | '(' expression ')' | Id['++','--']
-     * 
- * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { - if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( matchx("true" ) ) return con(1); - if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR); - if( match("(") ) return require(parseAsgn(), ")"); - if( matchx("new" ) ) return alloc(); - // Expect an identifier now - ScopeNode.Var n = requireLookupId("an identifier or expression"); - Node rvalue = _scope.in(n); - if( rvalue._type == Type.BOTTOM ) - throw error("Cannot read uninitialized field '"+n._name+"'"); - // Check for assign-update, x += e0; - char ch = _lexer.matchOperAssign(); - if( ch==0 ) return rvalue; - if( n._final ) - throw error("Cannot reassign final '"+n._name+"'"); - Node op = switch(ch) { - case '+' -> new AddNode(rvalue,null); - case '-' -> new SubNode(rvalue,null); - case '*' -> new MulNode(rvalue,null); - case '/' -> new DivNode(rvalue,null); - case '&' -> new AndNode(rvalue,null); - case '|' -> new OrNode(rvalue,null); - case '^' -> new XorNode(rvalue,null); - case Lexer.SHL -> new ShlNode(rvalue,null); - case Lexer.SAR -> new SarNode(rvalue,null); - case Lexer.SHR -> new ShrNode(rvalue,null); - case 1 -> new AddNode(rvalue,con( 1)); - case (char)-1 -> new AddNode(rvalue,con(-1)); - default -> throw Utils.TODO(); - }; - // Return pre-value (x+=1) or post-value (x++) - boolean pre = op.in(2)==null; - // Parse RHS argument as needed - if( pre ) - { op.keep().setDef(2,parseAsgn()); op.unkeep(); } - else rvalue.keep(); // Keep post-value across peeps - op = zsMask(peep(op),n.type()); - _scope.update(n,op); - return pre ? op : rvalue.unkeep(); - } - - ScopeNode.Var requireLookupId(String msg) { - String id = _lexer.matchId(); - if( id == null || KEYWORDS.contains(id) ) - throw errorSyntax(msg); - ScopeNode.Var n = _scope.lookup(id); - if( n==null ) - throw error("Undefined name '" + id + "'"); - return n; - } - - /** - Parse an allocation - */ - private Node alloc() { - Type t = type(); - if( t==null ) throw error("Expected a type"); - // Parse ary[ length_expr ] - if( match("[") ) { - Node len = parseAsgn(); - if( !(len._type instanceof TypeInteger) ) - throw error("Cannot allocate an array with length "+len._type); - require("]"); - TypeMemPtr tmp = typeAry(t); - return newArray(tmp._obj,len); - } - - if( !(t instanceof TypeMemPtr tmp) ) - throw error("Cannot allocate a "+t.str()); - - // Parse new struct { default_initialization } - StructNode s = INITS.get(tmp._obj._name); - if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'"); - - Field[] fs = s._ts._fields; - // if the object is fully initialized, we can skip a block here. - // Check for constructor block: - boolean hasConstructor = match("{"); - Ary init=s._inputs; int idx=0; - if( hasConstructor ) { - idx = _scope.nIns(); - // Push a scope, and pre-assign all struct fields. - _scope.push(); - for( int i=0; i init ) { - Field[] fs = obj._fields; - if( fs==null ) - throw error("Unknown struct type '" + obj._name + "'"); - Node prior = mem().keep(); - MemMergeNode input = new MemMergeNode(null); - for( Field f : fs ) input.alias(f._alias,prior); - // Constructor values follow control, memory, and allocation size. - Node[] ns = new Node[3+fs.length]; - ns[0] = ctrl(); - ns[1] = input.peephole(); - ns[2] = size; - for( int i=0; i ALTMP = new Ary<>(Node.class); - private Node newArray(TypeStruct ary, Node len) { - int base = ary.aryBase (); - int scale= ary.aryScale(); - Node size = peep(new AddNode(con(base),peep(new ShlNode(len.keep(),con(scale))))); - ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._type.makeInit())); - return newStruct(ary,size,0,ALTMP); - } - - // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.mem(); } - private void mem(Node n) { _scope.mem(n); } - - private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { - Node prior = mem().keep(); - Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init); - st.setDef(0,ctrl); - st = st.peephole(); - mem(new MemMergeNode(prior,alias,st).peephole()); - prior.unkeep(); - } - - /** - * Parse postfix expression. For now this is just a field - * expression, but in future could be array index too. - * - *
-     *     expr ('.' IDENTIFIER)* [ = expr ]
-     *     expr #
-     *     expr ('[' expr ']')* = [ = expr ]
-     * 
- */ - private Node parsePostfix(Node expr) { - String name = null; - if( match(".") ) name = requireId(); - else if( match("#") ) name = "#"; - else if( match("[") ) name = "[]"; - else return expr; // No postfix - - // Sanity check expr for being a reference - if( !(expr._type instanceof TypeMemPtr ptr) ) - throw error("Expected reference but got " + expr._type.str()); - - // Happens when parsing known dead code, which often has other typing - // issues. Since the code is dead, possibly due to inlining, lets not - // spoil the user experience with error messages. - if( ctrl()._type==Type.XCONTROL ) - // Exit out via parsing the trailing expression - return matchOpx('=','=') ? parseAsgn() : parsePostfix(con(Type.TOP)); - - // Sanity check field name for existing - TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name); - if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr + "'"); - TypeStruct base = tmp._obj; - int fidx = base.find(name); - if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - - // Get field type and layout offset from base type and field index fidx - Field f = base._fields[fidx]; // Field from field index - Type tf = f._type; - if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) - tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); - - expr.keep(); - // Field offset; fixed for structs, computed for arrays - Node off = (name.equals("[]") // If field is an array body - // Array index math - ? peep(new AddNode(con(base.aryBase()),peep(new ShlNode(require(parseAsgn(),"]"),con(base.aryScale()))))) - // Struct field offsets are hardwired - : con(base.offset(fidx))).keep(); - - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( matchOpx('=','=') ) { - Node val = parseAsgn().keep(); - Node lift = liftExpr( val, tf, f._final ); - - store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } - - Node load = new LoadNode(name, f._alias, tf.glb(), mem(), expr, off); - // Arrays include control, as a proxy for a safety range check - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) load.setDef(0,ctrl()); - load = peep(load); - - // ary[idx]++ or ptr.fld++ - if( matchx("++") || matchx("--") ) { - if( f._final && !f._fname.equals("[]") ) - throw error("Cannot reassign final '"+f._fname+"'"); - Node inc = peep(new AddNode(load,con( _lexer.peek(-1)=='+' ? 1 : -1))); - Node val = zsMask(inc,tf); - store(name,f._alias,tf,expr.unkeep(),off,val,false,base.isAry() ? ctrl() : null); - // And use the original loaded value as the result - } else expr.unkill(); - off.unkill(); - - return parsePostfix(load); - } - - - // zero/sign extend. "i" is limited to either classic unsigned (min==0) or - // classic signed (min=minus-power-of-2); max=power-of-2-minus-1. - private Node zsMask(Node val, Type t ) { - if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { - if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) - return val; - // Float rounding - return peep(new RoundF32Node(val)); - } - if( t0._min==0 ) // Unsigned - return peep(new AndNode(val,con(t0._max))); - // Signed extension - int shift = Long.numberOfLeadingZeros(t0._max)-1; - Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) - return val; - return peep(new SarNode(peep(new ShlNode(val,shf.keep())),shf.unkeep())); - } - - - /** - * Parse integer literal - * - *
-     *     integerLiteral: [1-9][0-9]* | [0]
-     *     floatLiteral: [digits].[digits]?[e [digits]]?
-     * 
- */ - private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - public static Node con( long con ) { return con(TypeInteger.constant(con)); } - public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } - public Node peep( Node n ) { - // Peephole, then improve with lexically scoped guards - return _scope.upcastGuard(n.peephole()); - } - - ////////////////////////////////// - // Utilities for lexical analysis - - // Return true and skip if "syntax" is next in the stream. - private boolean match (String syntax) { return _lexer.match (syntax); } - // Match must be "exact", not be followed by more id letters - private boolean matchx(String syntax) { return _lexer.matchx(syntax); } - private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } - // Return true and do NOT skip if 'ch' is next - private boolean peek(char ch) { return _lexer.peek(ch); } - private boolean peekIsId() { return _lexer.peekIsId(); } - - private int pos(int pos) { - int old = _lexer._position; - _lexer._position = pos; - return old; - } - private Type posT(int pos) { _lexer._position = pos; return null; } - - // Require and return an identifier - private String requireId() { - String id = _lexer.matchId(); - if (id != null && !KEYWORDS.contains(id) ) return id.intern(); - throw error("Expected an identifier, found '"+id+"'"); - } - - private String matchId() { - int old = pos(); - String id = _lexer.matchId(); - if( id==null ) return null; - if( !KEYWORDS.contains(id) ) return id; - pos(old); - return null; - } - - // Require an exact match - private Parser require(String syntax) { require(null, syntax); return this; } - private N require(N n, String syntax) { - if (match(syntax)) return n; - throw errorSyntax(syntax); - } - - RuntimeException errorSyntax(String syntax) { - return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); - } - - public static RuntimeException error( String errorMessage ) { - return new RuntimeException(errorMessage); - } - - //////////////////////////////////// - // Lexer components - - private static class Lexer { - - // Input buffer; an array of text bytes read from a file or a string - private final byte[] _input; - // Tracks current position in input buffer - private int _position = 0; - - /** - * Record the source text for lexing - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - public Lexer(byte[] buf) { - _input = buf; - } - - // Very handy in the debugger, shows the unparsed program - @Override - public String toString() { - return new String(_input, _position, _input.length - _position); - } - - // True if at EOF - private boolean isEOF() { - return _position >= _input.length; - } - - // Peek next character, or report EOF - private char peek() { - return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate - : (char) _input[_position]; - } - // Just crash if misused - public byte peek(int off) { return _input[_position+off]; } - - private char nextChar() { - char ch = peek(); - _position++; - return ch; - } - - // True if a white space - private boolean isWhiteSpace() { - return peek() <= ' '; // Includes all the use space, tab, newline, CR - } - - /** - * Return the next non-white-space character - */ - private void skipWhiteSpace() { - while( true ) { - if( isWhiteSpace() ) _position++; - // Skip // to end of line - else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '/') { - _position += 2; - while( !isEOF() && _input[_position] != '\n' ) _position++; - } else break; - } - } - - // Next non-white-space character, or EOF - public char nextXChar() { skipWhiteSpace(); return nextChar(); } - - // Return true, if we find "syntax" after skipping white space; also - // then advance the cursor past syntax. - // Return false otherwise, and do not advance the cursor. - boolean match(String syntax) { - skipWhiteSpace(); - int len = syntax.length(); - if (_position + len > _input.length) return false; - for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) - return false; - _position += len; - return true; - } - - // Match must be exact and not followed by more ID characters. - // Prevents identifier "ifxy" from matching an "if" statement. - boolean matchx(String syntax) { - if( !match(syntax) ) return false; - if( !isIdLetter(peek()) ) return true; - _position -= syntax.length(); - return false; - } - boolean matchOpx(char c0, char c1) { - skipWhiteSpace(); - if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) - return false; - _position++; - return true; - } - - private boolean peek(char ch) { - skipWhiteSpace(); - return peek()==ch; - } - - boolean peekIsId() { - skipWhiteSpace(); - return isIdStart(peek()); - } - - // Return an identifier or null - String matchId() { - return peekIsId() ? parseId() : null; - } - - // Used for errors - String getAnyNextToken() { - if (isEOF()) return ""; - if (isIdStart(peek())) return parseId(); - if (isNumber(peek())) return parseNumberString(); - if (isPunctuation(peek())) return parsePunctuation(); - return String.valueOf(peek()); - } - - - boolean isNumber(char ch) {return Character.isDigit(ch);} - - // Return a constant Type, either TypeInteger or TypeFloat - private Type parseNumber() { - int old = _position; - int len = isLongOrDouble(); - if( len > 0 ) { - if( len > 1 && _input[old]=='0' ) - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(new String(_input,old,len))); - } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); - } - private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - _position += len; - return new String(_input,old,len); - } - - // Return +len that ends a long - // Return -len that ends a double - private int isLongOrDouble() { - int old = _position; - char c; - while( Character.isDigit(c=nextChar()) ) ; - if( !(c=='e' || c=='.') ) - return --_position - old; - while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; - return -(--_position - old); - } - - // First letter of an identifier - private boolean isIdStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - // All characters of an identifier, e.g. "_x123" - private boolean isIdLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private String parseId() { - int start = _position; - while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); - } - - // - private boolean isPunctuation(char ch) { - return "=;[]<>()+-/*&|^".indexOf(ch) != -1; - } - - private String parsePunctuation() { - int start = _position; - return new String(_input, start, 1); - } - - // Next oper= character, or 0. - // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535) - // Disallow e.g. "arg--1" which should parse as "arg - -1" - // Distinct tags for the multi-character compound operators. - static final char SHL = 2, SAR = 3, SHR = 4; - - public char matchOperAssign() { - skipWhiteSpace(); - if( match("<<=" ) ) return SHL; - if( match(">>>=") ) return SHR; - if( match(">>=" ) ) return SAR; - if( _position+2 >= _input.length ) return 0; - char ch0 = (char)_input[_position]; - if( "+-/*&|^".indexOf(ch0) == -1 ) return 0; - char ch1 = (char)_input[_position+1]; - if( ch1 == '=' ) { _position += 2; return ch0; } - if( isIdLetter((char)_input[_position+2]) ) return 0; - if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; } - if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; } - return 0; - } - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Utils.java b/chapter17/src/main/java/com/seaofnodes/simple/Utils.java deleted file mode 100644 index 1346c3ea..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/Utils.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.seaofnodes.simple; - -import java.util.ArrayList; - -public class Utils { - public static RuntimeException TODO() { return TODO("Not yet implemented"); } - public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } - - /** - * Fast, constant-time, element removal. Does not preserve order - * - * @param array ArrayList to modify - * @param i element to be removed - * @return element removed - */ - public static void del(ArrayList array, int i) { - E last = array.removeLast(); - if (i < array.size()) array.set(i, last); - } - - /** - * Search a list for an element by reference - * - * @param ary List to search in - * @param x Object to be searched - * @return >= 0 on success, -1 on failure - */ - public static int find( ArrayList ary, E x ) { - for( int i=0; i= 0 on success, -1 on failure - */ - public static int find( E[] ary, E x ) { - for( int i=0; i>>n); } - - // Fold a 64bit hash into 32 bits - public static int fold( long x ) { return (int)((x>>32) ^ x); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index fd713156..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class AddFNode extends Node { - public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override protected String format() { return "(%1+%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()+i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t2 = in(2)._type; - - // Add of 0. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java deleted file mode 100644 index 5eb0b0a3..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.*; - - -public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Add"; } - - @Override protected String format() { return "(%1+%2)"; } - - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) { - if (i1.isConstant() && i2.isConstant()) - return TypeInteger.constant(i1.value()+i2.value()); - // Fold ranges like {0-1} + {2-3} into {2-4}. - if( !overflow(i1._min,i2._min) && - !overflow(i1._max,i2._max) ) - return TypeInteger.make(i1._min+i2._min,i1._max+i2._max); - } - return TypeInteger.BOT; - } - - static boolean overflow( long x, long y ) { - if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow - return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow - } - @Override - public Node idealize () { - Node lhs = in(1); - Node rhs = in(2); - if( rhs.err() != null ) return null; - Type t2 = rhs._type; - - // Add of 0. We do not check for (0+x) because this will already - // canonicalize to (x+0) - if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 ) - return lhs; - - // Add of same to a multiply by 2 - if( lhs==rhs ) - return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); - - // Goal: a left-spine set of adds, with constants on the rhs (which then fold). - - // Move non-adds to RHS - if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) - return swap12(); - - // x+(-y) becomes x-y - if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); - - // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) - - // Do we have x + (y + z) ? - // Swap to (x + y) + z - // Rotate (add add add) to remove the add on RHS - if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); - - // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) - // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); - - // Now we only see (add add non) - - // Dead data cycle; comes about from dead infinite loops. Do nothing, - // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) - return null; - - // Do we have (x + con1) + con2? - // Replace with (x + (con1+con2) which then fold the constants - // lhs.in(2) is con1 here - // If lhs.in(2) is not a constant, we add ourselves as a dependency - // because if it later became a constant then we could make this - // transformation. - if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); - - - // Do we have ((x + (phi cons)) + con) ? - // Do we have ((x + (phi cons)) + (phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - Node phicon = phiCon(this,true); - if( phicon!=null ) return phicon; - - // Now we sort along the spine via rotates, to gather similar things together. - - // Do we rotate (x + y) + z - // into (x + z) + y ? - if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - - return null; - } - - // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - static Node phiCon(Node op, boolean rotate) { - Node lhs = op.in(1); - Node rhs = op.in(2); - if( rhs._type==TypeInteger.TOP ) return null; - // LHS is either a Phi of constants, or another op with Phi of constants - PhiNode lphi = pcon(lhs,op); - if( rotate && lphi==null && lhs.nIns() > 2 ) { - // Only valid to rotate constants if both are same associative ops - if( lhs.getClass() != op.getClass() ) return null; - lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push - } - if( lphi==null ) return null; - - // RHS is a constant or a Phi of constants - if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) - return null; - - // If both are Phis, must be same Region - if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) - return null; - - // Note that this is the exact reverse of Phi pulling a common op down - // to reduce total op-count. We don't get in an endless push-up - // push-down peephole cycle because the constants all fold first. - Node[] ns = new Node[lphi.nIns()]; - ns[0] = lphi.in(0); - // Push constant up through the phi: x + (phi con0+con0 con1+con1...) - for( int i=1; i hi._nid; - } - - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } - @Override public String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java deleted file mode 100644 index bd03d6cb..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class AndNode extends Node { - public AndNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "And"; } - - @Override protected String format() { return "(%1&%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if( i0.isConstant() && i1.isConstant() ) - return TypeInteger.constant(i0.value()&i1.value()); - // Sharpen allowed bits if either value is narrowed - long mask = i0.mask() & i1.mask(); - return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // And of -1. We do not check for (-1&x) because this will already - // canonicalize to (x&-1) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x & (phi cons)) & con) ? - // Do we have ((x & (phi cons)) & (phi cons)) ? - // Push constant up through the phi: x & (phi con0&con0 con1&con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AndNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '&' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '&' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java deleted file mode 100644 index 6efde623..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeMemPtr; - -import static com.seaofnodes.simple.type.TypeInteger.*; - -abstract public class BoolNode extends Node { - - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - abstract String op(); // String opcode name - - @Override - public String label() { return getClass().getSimpleName(); } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.open().n(in(1)).p(op()).n(in(2)).close(); - } - - @Override - public TypeInteger compute() { - Type t1 = in(1)._type; - Type t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return BOOL.dual(); - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); - if( t1 instanceof TypeFloat f1 && - t2 instanceof TypeFloat f2 && - f1.isConstant() && f2.isConstant() ) - return TypeInteger.constant(doOp(f1.value(), f2.value()) ? 1 : 0); - return BOOL; - } - - TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } - boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } - - @Override - public Node idealize() { - // Compare of same - if( in(1)==in(2) ) - return new ConstantNode(doOp(ZERO,ZERO)); - - // Equals pushes constant to the right; 5==X becomes X==5. - if( this instanceof EQ ) { - if( !(in(2) instanceof ConstantNode) ) { - // con==noncon becomes noncon==con - if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); - } - // Equals X==0 becomes a !X - if( (in(2)._type == ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) - return new NotNode(in(1)); - } - - // Do we have ((phi cons) cmp con) ? - // Do we have ((phi cons) cmp (phi cons)) ? - // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) - Node phicon = AddNode.phiCon(this,this instanceof EQ); - if( phicon!=null ) return phicon; - - return null; - } - - public static class EQ extends BoolNode { - public EQ(Node lhs, Node rhs) { super(lhs,rhs); } - String op() { return "=="; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1==i2 && i1.isConstant() ) return TRUE; - if( i1._max < i2._min || i1._min > i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } - Node copyF() { return new EQF(null,null); } - } - public static class LT extends BoolNode { - public LT(Node lhs, Node rhs) { super(lhs,rhs); } - String op() { return "<" ; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1._max < i2._min ) return TRUE; - if( i1._min >= i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } - Node copyF() { return new LTF(null,null); } - } - public static class LE extends BoolNode { - public LE(Node lhs, Node rhs) { super(lhs,rhs); } - String op() { return "<="; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( i1._max <= i2._min ) return TRUE; - if( i1._min > i2._max ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } - Node copyF() { return new LEF(null,null); } - } - - public static class EQF extends EQ { public EQF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } } - public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } } - public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java deleted file mode 100644 index 84f97189..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import java.util.BitSet; - -/** - * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; - * those aliases must be represented by parallel precise MemPhis at the same - * program slice. - */ -public class BulkMemPhiNode extends PhiNode { - - private static final BitSet EMPTY = new BitSet(); - - // Excluded Aliases; empty means NO exclusions. - // Each split clones the exclusions; published sets are never mutated. - public final BitSet _aliases; - - public BulkMemPhiNode(String label, Node... inputs) { - this(label,EMPTY,inputs); - } - - private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label,TypeMem.BOT,inputs); - _aliases = aliases; - } - - public boolean isSplit(int alias) { return _aliases.get(alias); } - - @Override public String label() { return "BulkPhi"+_aliases; } - - @Override - public Type compute() { - Type t = super.compute(); - return t instanceof TypeMem mem ? mem : - t==Type.BOTTOM ? TypeMem.BOT : - TypeMem.TOP; - } - - @Override - public Node idealize() { - Node progress = super.idealize(); - if( progress != null ) return progress; - if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) - return null; // Input is in-progress - - // "Peek through" a MemMerge that covers this alias set on its default - for( int i=1; i= mmm.nIns() || mmm.in(alias)==null) ) - return false; - return true; - } - - - // True if the MemMerge has no precise slice still covered by this Phi. - private boolean canPeek(MemMergeNode mmm) { - return mmm.in(1)!=null && missingAlias(mmm)==0; - } - - // Return an alias required by an input but still covered by this Phi, or 0. - private int inputAlias(Node n) { - return switch(n) { - case ProjNode proj -> 0; - case ConstantNode con -> 0; - case MemMergeNode mmm -> missingAlias(mmm); - case MemOpNode mem -> unsplit(mem._alias); - case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw new AssertionError("Unexpected memory input "+n); - }; - } - - // Return an alias required by a user but still covered by this Phi, or 0. - private int outputAlias(Node use) { - // This decision depends backwards on an immediate user's alias/set. - // Normal graph-neighbor enqueueing flows from defs to uses, so retain - // an explicit forward dependency to revisit this Phi when the user - // sharpens. - use.addDepForwards(this); - return switch(use) { - case ScopeNode scope -> 0; - case MemMergeNode mmm -> missingAlias(mmm); - case BulkMemPhiNode bulk -> missingAlias(bulk); - case MemOpNode mem -> unsplit(mem._alias); - case MemPhiNode phi -> unsplit(phi._alias); - default -> { - assert use instanceof ReturnNode - : "Unexpected bulk-memory user "+use; - yield 0; - } - }; - } - - // Alias zero/one is bulk, and an already excluded precise alias cannot - // trigger another split of this Phi. - private int unsplit(int alias) { - if( alias <= 1 ) return 0; - assert !_aliases.get(alias); - return alias; - } - - // First alias excluded by the neighboring bulk Phi but not by this one. - private int missingAlias(BulkMemPhiNode bulk) { - for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( !_aliases.get(alias) ) - return alias; - return 0; - } - - // Every explicit slot requests that alias, including a partial allocation - // input whose slot still points to this bulk Phi. - private int missingAlias(MemMergeNode mmm) { - for( int alias=2; alias= 0; old = _aliases.nextSetBit(old+1) ) - { assert old != alias; mmm.alias(old,precisePhi(old));} - mmm.alias(alias,precise); - assert checkMerge(mmm); - return mmm; - } - - // Find or make the precise Phi parallel to this bulk Phi at the same merge. - private Node precisePhi(int alias) { - MemPhiNode mphi = _findPhi(alias); - if( mphi!=null ) return mphi; - - mphi = new MemPhiNode("$"+alias,alias); - mphi.addDef(region()); - // Due to cycles, must set before calling peephole - mphi.setType(TypeMem.make(alias,Type.BOTTOM)); - for( int i=1; i= 0; alias = bulk._aliases.nextSetBit(alias+1) ) - if( alias >= mmm.nIns() || mmm.in(alias)==null ) - return false; - return true; - } - - @Override public boolean eq(Node n) { - return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); - } - - @Override int hash() { return _aliases.hashCode(); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java deleted file mode 100644 index e85fc6ae..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; - -import java.util.BitSet; -import java.util.HashSet; -import java.util.HashMap; - -/** Control Flow Graph Nodes - *

- * CFG nodes have a immediate dominator depth (idepth) and a loop nesting - * depth(loop_depth). - *

- * idepth is computed lazily upon first request, and is valid even in the - * Parser, and is used by peepholes during parsing and afterward. - *

- * loop_depth is computed after optimization as part of scheduling. - * - */ -public abstract class CFGNode extends Node { - - public CFGNode(Node... nodes) { super(nodes); } - - public CFGNode cfg(int idx) { return (CFGNode)in(idx); } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return false; } - - // ------------------------------------------------------------------------ - /** - * Immediate dominator tree depth, used to approximate a real IDOM during - * parsing where we do not have the whole program, and also peepholes - * change the CFG incrementally. - *

- * See {@link ...} - */ - public char _idepth; - public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } - // Zero depth means uncached. Check before narrowing so overflow cannot wrap. - final int cacheIDepth(int depth) { - assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; - return _idepth = (char)depth; - } - - - // Return the immediate dominator of this Node and compute dom tree depth. - public CFGNode idom(Node dep) { return cfg(0); } - public final CFGNode idom() { return idom(null); } - - // Return the LCA of two idoms - public CFGNode domLCA(CFGNode rhs, Node dep) { - if( rhs==null ) return this; - CFGNode lhs = this; - while( lhs != rhs ) { - if( lhs==null || rhs==null ) return null; - int comp = lhs.idepth() - rhs.idepth(); - if( comp >= 0 ) lhs = (dep==null ? lhs : (CFGNode)lhs.addDep(dep)).idom(); - if( comp <= 0 ) rhs = (dep==null ? rhs : (CFGNode)rhs.addDep(dep)).idom(); - } - return lhs; - } - - // ------------------------------------------------------------------------ - // Loop nesting - public LoopNode loop() { return _ltree._head; } - public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } - - LoopTree _ltree; - int _pre; // Pre-order numbers for loop tree finding - private static class LoopTree { - LoopTree _par; - final LoopNode _head; - int _depth; - LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return "LOOP"+_head._nid; } - int depth() { - return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; - } - } - - // ------------------------------------------------------------------------ - // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves - // also refer to *their* containing LoopNode, as well as have their depth. - // Start is a LoopNode which contains all at depth 1. - public void buildLoopTree(StopNode stop) { - _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); - _bltWalk(2,stop, new BitSet()); - } - private int _bltWalk(int pre, StopNode stop, BitSet post) { - // Pre-walked? - if( _pre!=0 ) return pre; - _pre = pre++; - // Pre-walk - for( Node use : _outputs ) - if( use instanceof CFGNode usecfg ) - pre = usecfg._bltWalk(pre,stop,post); - - // Post-order work: find innermost loop - LoopTree inner = null, ltree; - for( Node use : _outputs ) { - if( !(use instanceof CFGNode usecfg) ) continue; - // Child visited but not post-visited? - if( !post.get(usecfg._nid) ) { - // Must be a backedge to a LoopNode then - ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg); - } else { - // Take child's loop choice, which must exist - ltree = usecfg._ltree; - // If falling into a loop, use the target loop's parent instead - if( ltree._head == usecfg ) { - if( ltree._par == null ) - // This loop never had an If test choose to take its - // exit, i.e. it is a no-exit infinite loop. - ltree._par = ltree._head.forceExit(stop)._ltree; - ltree = ltree._par; - } - } - // Sort inner loops. The decision point is some branch far removed - // from either loop head OR either backedge so requires pre-order - // numbers to figure out innermost. - if( inner == null ) { inner = ltree; continue; } - if( inner == ltree ) continue; // No change - LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree; - inner = ltree._head._pre > inner._head._pre ? ltree : inner; - inner._par = outer; - } - // Set selected loop - if( inner!=null ) - _ltree = inner; - // Tag as post-walked - post.set(_nid); - return pre; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/CProjNode.java deleted file mode 100644 index 53348b5a..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeTuple; - -public class CProjNode extends CFGNode { - - // Which slice of the incoming multipart value - public final int _idx; - - // Debugging label - public final String _label; - - public CProjNode(Node ctrl, int idx, String label) { - super(ctrl); - _idx = idx; - _label = label; - } - - @Override public String label() { return _label; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { return p.p(_label); } - - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean blockHead() { return true; } - - public CFGNode ctrl() { return cfg(0); } - - @Override - public Type compute() { - Type t = ctrl()._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override - public Node idealize() { - if( ctrl()._type instanceof TypeTuple tt ) { - if( tt._types[_idx]==Type.XCONTROL ) - return Parser.XCTRL; // We are dead - if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes - return ctrl().in(0); // We become our input control - } - - // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not ) - return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - - return null; - } - - @Override - boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } - - @Override - int hash() { return _idx; } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index 9ade5a50..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.IterPeeps; - - -// Upcast (join) the input to a t. Used after guard test to lift an input. -// Can also be used to make a type-assertion if ctrl is null. -public class CastNode extends Node { - private final Type _t; - public CastNode(Type t, Node ctrl, Node in) { - super(ctrl, in); - _t = t; - setType(compute()); - } - - @Override public String label() { return "("+_t.str()+")"; } - - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()).n(in(1)); - } - - @Override - public Type compute() { - return in(1)._type.join(_t); - } - - @Override - public Node idealize() { - return in(1)._type.isa(_t) ? in(1) : null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ConstantNode.java deleted file mode 100644 index 426736ed..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; - - -/** - * A Constant node represents a constant value. At present, the only constants - * that we allow are integer literals; therefore Constants contain an integer - * value. As we add other types of constants, we will refactor how we represent - * Constants. - *

- * Constants have no semantic inputs. However, we set Start as an input to - * Constants to enable a forward graph walk. This edge carries no semantic - * meaning, and it is present solely to allow visitation. - *

- * The Constant's value is the value stored in it. - */ -public class ConstantNode extends Node { - final Type _con; - public ConstantNode( Type type ) { - super(Parser.START); - _con = type; - } - - @Override protected String repeatName() { - return _con==null || _con.toString().length()<=32 ? null : uniqueName(); - } - - @Override - public String label() { return "Con"; } - - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(_con); - } - - @Override public boolean isMultiTail() { return true; } - - @Override - public Type compute() { return _con; } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { - ConstantNode con = (ConstantNode)n; // Contract - return _con==con._con; - } - - @Override - int hash() { return _con.hashCode(); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index a3f4318d..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class DivFNode extends Node { - public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && - in(2)._type instanceof TypeFloat i1) { - if (i0.isConstant() && i1.isConstant()) - return TypeFloat.constant(i0.value()/i1.value()); - } - return in(1)._type.meet(in(2)._type); - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Div of 1. - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java deleted file mode 100644 index cd749a03..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Div"; } - - @Override protected String format() { return "(%1/%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) { - if (i1.isConstant() && i2.isConstant()) - return i2.value() == 0 - ? TypeInteger.ZERO - : TypeInteger.constant(i1.value()/i2.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - // Div of 1. - if( in(2)._type == TypeInteger.TRUE ) - return in(1); - return null; - } - - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/IfNode.java deleted file mode 100644 index 2b53b3a3..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeTuple; -import java.util.HashSet; - -public class IfNode extends CFGNode implements MultiNode { - - public IfNode(Node ctrl, Node pred) { - super(ctrl, pred); - IterPeeps.add(this); // Because idoms are complex, just add it - } - - @Override - public String label() { return "If"; } - - @Override protected String format() { return "if( %1 )"; } - - @Override public boolean isMultiHead() { return true; } - - public Node ctrl() { return in(0); } - public Node pred() { return in(1); } - - @Override - public Type compute() { - // If the If node is not reachable then neither is any following Proj - if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) - return TypeTuple.IF_NEITHER; - Node pred = pred(); - Type t = pred._type; - // High types mean NEITHER side is reachable. - // Wait until the type falls to decide which way to go. - if( t == Type.TOP || t == TypeInteger.TOP ) - return TypeTuple.IF_NEITHER; - // If constant is 0 then false branch is reachable - // Else true branch is reachable - if( t.isConstant() ) - return t.makeInit()==t ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; - // Integers allow non-zero ranges: "1-65535" - if( t.makeZero().meet(t) != t ) - return TypeTuple.IF_TRUE; - - return TypeTuple.IF_BOTH; - } - - @Override - public Node idealize() { - // Hunt up the immediate dominator tree. If we find an identical if - // test on either the true or false branch, that side wins. - if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { - setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); - return this; - } - return null; - } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java deleted file mode 100644 index c5cc381a..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ /dev/null @@ -1,221 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.*; -import java.util.BitSet; -import java.util.ArrayList; - -/** - * Load represents extracting a value from inside a memory object, - * in chapter 10 this means Struct fields. - */ -public class LoadNode extends MemOpNode { - - /** - * Load a value from a ptr.field. - * - * @param name The field we are loading - * @param mem The memory alias node - this is updated after a Store - * @param ptr The ptr to the struct base from where we load a field - * @param off The offset inside the struct base - */ - public LoadNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(name, alias, glb, mem, ptr, off); - } - - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && !clobbered(dep); - } - - // Check only immediate memory users. Stores clobber memory; Phis and - // aggregates might lead to a clobber, so stop rather than search further. - private boolean clobbered(Node dep) { - Node mem = mem(); - mem.addDepForwards(dep); - for( Node use : mem._outputs ) { - if( use==null ) continue; - use.addDepForwards(dep); - if( use instanceof StoreNode || use instanceof PhiNode || - use instanceof MemMergeNode || - use instanceof NewNode ) return true; - } - return false; - } - - // Debugger label - @Override public String label() { return "ld_"+mlabel(); } - @Override - protected ExprPrinter _print1(ExprPrinter p) { return p.p(".").p(_name); } - - @Override - public Type compute() { - Type t = MemMergeNode.contents(mem(),_alias,this); - // Update declared forward ref to the actual. - if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - return err()==null ? _declaredType.join(t) : _declaredType; - } - - @Override - public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,IterPeeps.add(merge.alias(_alias))); - return this; - } - Node ptr = ptr(); - - // Simple Load-after-Store on same address. - if( mem() instanceof StoreNode st && - _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return st.val(); - } - - // Simple Load-after-New on same address. - if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(0) ) // Must check same object - return nnn.in(nnn.findAlias(_alias)); // Load from New init - - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - Node mem = mem(); - outer: - while( true ) { - mem.addDep(this); - switch( mem ) { - case MemMergeNode merge: - mem = merge.alias(_alias); - break; - case StoreNode st: - if( _alias==st._alias && ptr == st.ptr() && off() == st.off() ) - return castRO(st.val()); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: break outer; // Assume related - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: - if( mproj.in(0) instanceof NewNode nnn1 ) { - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return castRO(nnn1.in(nnn1.findAlias(_alias))); // Load from New init - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.mem();// Bypass unrelated New - break; - } else break outer; - default: - break outer; - } - } - - // Push a Load up through a Phi, as long as it collapses on at least - // one arm. If at a Loop, the backedge MUST collapse - else we risk - // spinning the same transform around the loop indefinitely. - // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): - // if( pred ) ptr.x = e0; val = pred ? e0 - // else ptr.x = e1; : e1; - // val = ptr.x; ptr.x = val; - if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && - // Offset can be hoisted - off() instanceof ConstantNode && - // Pointer can be hoisted - hoistPtr(ptr,memphi) ) { - - // Profit on RHS/Loop backedge - if( profit(memphi,2) || - // Else must not be a loop to count profit on LHS. - (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { - Node ld1 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); - } - } - - return null; - } - - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); - ld.setDef(0,in(0)); - return ld.peephole(); - } - - private static boolean neverAlias( Node ptr1, Node ptr2 ) { - return ptr1.in(0) != ptr2.in(0) && - // Unrelated allocations - ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode && - ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; - } - - private static boolean hoistPtr(Node ptr, PhiNode memphi ) { - // Can I hoist ptr above the Region? - if( !(memphi.region() instanceof RegionNode r) ) - return false; // Dead or dying Region/Phi - // If ptr from same Region, then yes, just use hoisted split pointers - if( ptr instanceof PhiNode pphi && pphi.region() == r ) - return true; - - // No, so can we lift this ptr? - CFGNode cptr = ptr.cfg0(); - if( cptr != null ) - // Pointer is controlled high - // TODO: Really needs to be the LCA of all inputs is high - return cptr.idepth() <= r.idepth(); - - // Dunno without a longer walk - return false; - } - - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - px.addDep(this); - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; - return false; - } - - // Read-Only is a deep property, and cannot be cast-away - private Node castRO(Node rez) { - if( ptr()._type.isFinal() && !rez._type.isFinal() ) - return new ReadOnlyNode(rez).peephole(); - return rez; - } - - // Memory writers which must follow this read. A packaging node is not a - // write, but can hide New's input, so follow this alias through aggregates. - public ArrayList antiDeps() { - ArrayList deps = new ArrayList<>(); - antiDeps(mem(),deps,new BitSet()); - return deps; - } - - private void antiDeps(Node mem, ArrayList deps, BitSet visit) { - if( visit.get(mem._nid) ) return; - visit.set(mem._nid); - for( Node use : mem._outputs ) { - if( use instanceof MemMergeNode merge ) { - Node slice = _alias 1; - while( nIns()<=alias ) addDef(null); - setDef(alias,mem); - } - - // Scalar contents of one alias. New's field inputs supply the initialized type; - // join it with the incoming contents for all previously allocated objects. - // Phis use their cached types, so this query does not recurse around loops. - static Type contents(Node mem, int alias, Node dep) { - mem.addDep(dep); - if( mem instanceof MemMergeNode merge ) - return contents(merge.alias(alias),alias,dep); - if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { - assert proj._idx==1 && nnn.field(alias)!=null; - nnn.addDep(dep); - Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); - return contents(nnn.mem(),alias,dep).meet(init._type); - } - // There are no heap arguments in this chapter. Before the first New, - // the set of allocated objects (and hence stored values) is empty. - if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP; - if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; - if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; - return Type.BOTTOM; - } - - @Override public String label() { return "MemMerge"; } - @Override public boolean isMem() { return true; } - @Override public Type compute() { return TypeMem.BOT; } - @Override protected ExprPrinter _print1(ExprPrinter p) { - p.p("MEM["); - for( int i=1; i=nIns() || in(i)==null) ) - alias(i,mem.in(i)); - IterPeeps.add(mem.in(1)); - setDef(1,mem.in(1)); - return this; - } - assert BulkMemPhiNode.checkMerge(this); - return progress ? this : null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java deleted file mode 100644 index 0bf9b6ac..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - -/** - * Convenience common base for Load and Store. - */ -public abstract class MemOpNode extends Node { - - public final String _name; - public final int _alias; - // Declared type; not final because it might be a forward-reference - // which will be lazily improved when the reference is declared. - public Type _declaredType; - - public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(null, mem, ptr, off); - _name = name; - _alias = alias; - _declaredType = glb; - } - public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value) { - this(name, alias, glb, mem, ptr, off); - addDef(value); - } - - // - static String mlabel(String name) { return name.equals("[]") ? "ary" : (name.equals("#") ? "len" : name); } - String mlabel() { return mlabel(_name); } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node off() { return in(3); } - - // Extra conditions for factoring matching memory operations through a Phi. - // The caller has already checked the opcode, input shape and control. - boolean canDrop(MemOpNode other, Node dep) { - ptr().addDep(dep); - return _alias==other._alias && _declaredType==other._declaredType && err()==null; - } - - @Override - boolean eq(Node n) { - MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" - } - - @Override - int hash() { return _alias; } - - @Override - String err() { - Type ptr = ptr()._type; - // Already an error, but better error messages come from elsewhere - if( ptr == Type.BOTTOM ) return null; - // Better be a not-nil TMP - if( ptr instanceof TypeMemPtr tmp && !tmp._nil ) - return null; - return "Might be null accessing '" + _name + "'"; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java deleted file mode 100644 index 6aae00f9..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; - -/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ -public class MemPhiNode extends PhiNode { - public final int _alias; - - public MemPhiNode(String label, int alias, Node... inputs) { - super(label,TypeMem.make(alias,Type.BOTTOM),inputs); - assert alias > 1; - _alias = alias; - } - - @Override public String label() { return "MemPhi_"+_alias; } - - @Override public Type compute() { - assert BulkMemPhiNode.checkMem(this,_alias); - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; - if( r.inProgress() ) return _declaredType; - Type t = Type.TOP; - for( int i=1; i _print1(ExprPrinter p) { return p.p("Never"); } - - @Override public Type compute() { return TypeTuple.IF_BOTH; } - - @Override public Node idealize() { return null; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java deleted file mode 100644 index 51341c41..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - -/** Allocate and initialize an object. Inputs {ctrl, $mem, size, fields...}; - * results {ptr, $mem}. - * The memory input and result cover only the aliases in the allocated struct. - */ -public class NewNode extends Node implements MultiNode { - - public final TypeMemPtr _ptr; - - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - _ptr = ptr; - assert nodes.length==3+ptr._obj._fields.length; - assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; - assert nodes[1]._type instanceof TypeMem; - assert nodes[2]._type instanceof TypeInteger; - for( int i=3; i _print1(ExprPrinter p) { - return p.p("new ").p(_ptr._obj.str()); - } - - // Find matching alias input - int findAlias(int alias) { - return 3+_ptr._obj.findAlias(alias); - } - - - @Override - public TypeTuple compute() { - return TypeTuple.make(_ptr,TypeMem.BOT); - } - - @Override - public Node idealize() { return null; } - - @Override - boolean eq(Node n) { return this == n; } - - @Override - int hash() { return _ptr.hashCode(); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java deleted file mode 100644 index bd1fdffa..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java +++ /dev/null @@ -1,611 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.BaseNode; - -import com.seaofnodes.simple.Parser; - -import com.seaofnodes.simple.Ary; -import com.seaofnodes.simple.IRPrinter; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import java.util.*; -import java.util.function.Function; - -/** - * All Nodes in the Sea of Nodes IR inherit from the Node class. - * The Node class provides common functionality used by all subtypes. - * Subtypes of Node specialize by overriding methods. - */ -public abstract class Node extends BaseNode implements OutNode, Cloneable { - - /** - * Inputs to the node. These are use-def references to Nodes. - *

- * Generally fixed length, ordered, nulls allowed, no unused trailing space. - * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a {@link CFGNode} node. - */ - public Ary _inputs; - - /** - * Outputs reference Nodes that are not null and have this Node as an - * input. These nodes are users of this node, thus these are def-use - * references to Nodes. - *

- * Outputs directly match inputs, making a directed graph that can be - * walked in either direction. These outputs are typically used for - * efficient optimizations but otherwise have no semantics meaning. - */ - public Ary _outputs; - - /** - * Current computed type for this Node. This value changes as the graph - * changes and more knowledge is gained about the program. - */ - public Type _type; - - /** - * A private Global Static mutable counter, for unique node id generation. - * To make the compiler multithreaded, this field will have to move into a TLS. - * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. - * */ - private static int UNIQUE_ID = 1; - public static int UID() { return UNIQUE_ID; } - - protected Node(Node... inputs) { - super(UNIQUE_ID++); // allocate unique dense ID - _inputs = new Ary<>(Node.class); - Collections.addAll(_inputs,inputs); - _outputs = new Ary<>(Node.class); - for( Node n : _inputs ) - if( n != null ) - n.addUse( this ); - } - - // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); - - public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - - public boolean isMultiHead() { return false; } - public boolean isMultiTail() { return false; } - - // ------------------------------------------------------------------------ - // Graph Node & Edge manipulation - - @Override public String typeName() { return _type==null ? null : _type.toString(); } - - /** - * Gets the ith input node - * @param i Offset of the input node - * @return Input node or null - */ - public Node in(int i) { return _inputs.get(i); } - public Node out(int i) { return _outputs.get(i); } - @Override public Ary outs() { return _outputs; } - - public int nIns() { return _inputs.size(); } - - public int nOuts() { return _outputs.size(); } - - public boolean isUnused() { return nOuts() == 0; } - - public CFGNode cfg0() { return (CFGNode)in(0); } - - /** - * Change a def into a Node. Keeps the edges correct, by removing - * the corresponding use->def edge. This may make the original - * def go dead. This function is co-recursive with {@link #kill}. - *

- - * This method is the normal path for altering a Node, because it does the - * proper default edge maintenance. It also immediately kills - * Nodes that lose their last use; at times care must be taken to avoid - * killing Nodes that are being used without having an output Node. This - * definitely happens in the middle of recursive {@link #peephole} calls. - * - * @param idx which def to set - * @param new_def the new definition - * @return new_def for flow coding - */ - public N setDef(int idx, N new_def ) { - unlock(); - Node old_def = in(idx); - if( old_def == new_def ) return new_def; // No change - // If new def is not null, add the corresponding def->use edge - // This needs to happen before removing the old node's def->use edge as - // the new_def might get killed if the old node kills it recursively. - if( new_def != null ) - new_def.addUse(this); - // Set the new_def over the old (killed) edge - _inputs.set(idx,new_def); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - moveDepsToWorklist(); - // Return new_def for easy flow-coding - return new_def; - } - - // Remove the numbered input, compressing the inputs in-place. This - // shuffles the order deterministically - which is suitable for Region and - // Phi, but not for every Node. If the def goes dead, it is recursively - // killed, which may include 'this' Node. - Node delDef(int idx) { - unlock(); - Node old_def = in(idx); - _inputs.del(idx); - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - old_def.moveDepsToWorklist(); - return this; - } - - // Insert the numbered input, sliding other inputs to the right - Node insertDef(int idx, Node new_def) { - _inputs.add(idx,null); - return setDef(idx,new_def); - } - - /** - * Add a new def to an existing Node. Keep the edges correct by - * adding the corresponding def->use edge. - * - * @param new_def the new definition, appended to the end of existing definitions - * @return new_def for flow coding - */ - public Node addDef(Node new_def) { - unlock(); - // Add use->def edge - _inputs.add(new_def); - // If new def is not null, add the corresponding def->use edge - if( new_def != null ) - new_def.addUse(this); - return new_def; - } - - // Breaks the edge invariants, used temporarily - protected void addUse(N n) { _outputs.add(n); } - - // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. - // Return true if the output list is empty afterward. - // Error is 'use' does not exist; ok for 'use' to be null. - protected boolean delUse( Node use ) { - _outputs.del(_outputs.find(use)); - moveDepsToWorklist(); // User-count and anti-dependence queries can now change. - return _outputs.isEmpty(); - } - - // Shortcut for "popping" until n nodes. A "pop" is basically a - // setDef(last,null) followed by lowering the nIns() count. - void popUntil(int n) { - unlock(); - while( nIns() > n ) { - Node old_def = _inputs.pop(); - if( old_def != null && // If it exists and - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - } - } - - /** - * Kill a Node with no uses, by setting all of its defs - * to null. This may recursively kill more Nodes, and is basically dead - * code elimination. - */ - public void kill( ) { - unlock(); - moveDepsToWorklist(); - assert isUnused(); // Has no uses, so it is dead - _type=null; // Flag as dead - while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes - Node old_def = _inputs.removeLast(); - if( old_def != null ) { - IterPeeps.add(old_def);// Revisit neighbor because removed use - if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def - } - } - assert isDead(); // Really dead now - } - - // Mostly used for asserts and printing. - public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - - // Shortcuts to stop DCE mid-parse - // Add bogus null use to keep node alive - public N keep() { addUse(null); return (N)this; } - // Remove bogus null. - public N unkeep() { delUse(null); return (N)this; } - // Test "keep" status - public boolean iskeep() { return _outputs.find(null) != -1; } - public void unkill() { - if( unkeep().isUnused() ) - kill(); - } - - // Replace self with nnn in the graph, making 'this' go dead - public void subsume( Node nnn ) { - assert nnn!=this; - while( nOuts() > 0 ) { - Node n = _outputs.removeLast(); - n.unlock(); - int idx = n._inputs.find(this); - n._inputs.set(idx,nnn); - n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. - nnn.addUse(n); - IterPeeps.addAll(n._outputs); - } - kill(); - } - - // ------------------------------------------------------------------------ - // Graph-based optimizations - - /** - * Try to peephole at this node and return a better replacement Node. - * Always returns some not-null Node (often this). - */ - public final Node peephole() { - var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; - if( obs != null ) obs.before(this); - Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n.peephole()); - if( obs != null ) obs.after(this, n == null ? null : rez, false); - return rez; - } - - /** - * Try to peephole at this node and return a better replacement Node if - * possible. We compute a {@link Type} and then check and replace: - *

    - *
  • if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}
  • - *
  • in a future chapter we will look for a - * Common Subexpression - * to eliminate.
  • - *
  • we ask the Node for a better replacement. The "better replacement" - * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes - * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing - * math constants, remove algebraic identities and generally simplify the - * code.
  • - * - * Unlike peephole above, this explicitly returns null for no-change, or not-null - * for a better replacement (which can be this). - *
- */ - public final Node peepholeOpt( ) { - ITER_CNT++; - // Compute initial or improved Type - Type old = setType(compute()); - - // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && !(this instanceof XCtrlNode) && _type.isHighOrConst() ) - return (_type==Type.XCONTROL ? new XCtrlNode() : new ConstantNode(_type)).peepholeOpt(); - - // Global Value Numbering - if( _hash==0 ) { - Node n = GVN.get(this); // Will set _hash as a side effect - if( n==null ) - GVN.put(this,this); // Put in table now - else { - // Because of random worklist ordering, the two equal nodes - // might have different types. Because of monotonicity, both - // types are valid. To preserve monotonicity, the resulting - // shared Node has to have the best of both types. - n.setType(n._type.join(_type)); - _hash = 0; // Clear, since it never went in the table - return deadCodeElim(n);// Return previous; does Common Subexpression Elimination - } - } - - // Ask each node for a better replacement - Node n = idealize(); - if( n != null ) // Something changed - return n; // Report progress - - if( old==_type ) ITER_NOP_CNT++; - return old==_type ? null : this; // Report progress - } - public static int ITER_CNT, ITER_NOP_CNT; - - // m is the new Node, self is the old. - // Return 'm', which may have zero uses but is alive nonetheless. - // If self has zero uses (and is not 'm'), {@link #kill} self. - private Node deadCodeElim(Node m) { - // If self is going dead and not being returned here (Nodes returned - // from peephole commonly have no uses (yet)), then kill self. - if( m != this && isUnused() && !isDead() ) { - // Killing self - and since self recursively kills self's inputs we - // might end up killing 'm', which we are returning as a live Node. - // So we add a bogus extra null output edge to stop kill(). - m.keep(); // Keep m alive - kill(); // Kill self because replacing with 'm' - m.unkeep(); // Okay to peephole m - } - return m; - } - - /** - * This function needs to be - * Monotonic - * as it is part of a Monotone Analysis Framework. - * See for example this set of slides. - *

- * For Chapter 2, all our Types are really integer constants, and so all - * the needed properties are trivially true, and we can ignore the high - * theory. Much later on, this will become important and allow us to do - * many fancy complex optimizations trivially... because theory. - *

- * {@link #compute()} needs to be stand-alone, and cannot recursively call compute - * on its inputs, because programs are cyclic (have loops!) and this will just - * infinitely recurse until stack overflow. Instead, compute typically - * computes a new type from the {@link #_type} field of its inputs. - */ - public abstract Type compute(); - - // Set the type. Assert monotonic progress. - // If changing, add users to worklist. - public Type setType(Type type) { - Type old = _type; - assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger - if( old == type ) return old; - _type = type; // Set _type late for easier assert debugging - IterPeeps.addAll(_outputs); - moveDepsToWorklist(); - return old; - } - - /** - * This function rewrites the current Node into a more "idealized" form. - * This is the bulk of our peephole rewrite rules, and we use this to - * e.g. turn arbitrary collections of adds and multiplies with mixed - * constants into a normal form that's easy for hardware to implement. - * Example: An array addressing expression: - *

   ary[idx+1]
- * might turn into Sea-of-Nodes IR: - *
   (ary+12)+((idx+1) * 4)
- * This expression can then be idealized into: - *
   ary + ((idx*4) + (12 + (1*4)))
- * And more folding: - *
   ary + ((idx<<2) + 16)
- * And during code-gen: - *
   MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation 
- *

- * {@link #idealize} has a very specific calling convention: - *

    - *
  • If NO change is made, return {@code null} - *
  • If ANY change is made, return not-null; this can be {@code this} - *
  • The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. - *
  • Any NEW nodes that are not directly returned DO call {@link #peephole}. - *
- *

- * Examples: - * - * - * - * - * - *
before after return comment
{@code (x+5) }{@code (x+5) }{@code null }No change
{@code (5+x) }{@code (x+5) }{@code this }Swapped arguments
{@code ((x+1)+2)}{@code (x+(1+2))}{@code (x+_) }Returns 2 new Nodes
- * - * The last entry deserves more discussion. The new Node {@code (1+2)} - * created in {@link #idealize} calls {@link #peephole} (which then folds - * into a constant). The other new Node {@code (x+3)} does not call - * peephole, because it is returned and peephole itself will recursively - * call peephole. - *

- * Since idealize calls peephole and peephole calls idealize, you must be - * careful that all idealizations are monotonic: all transforms - * remove some feature, so that the set of available transforms always - * shrinks. If you don't, you risk an infinite peephole loop! - * - * @return Either a new or changed node, or null for no changes. - */ - public abstract Node idealize(); - - // Some of the peephole rules get complex, and search further afield than - // just the nearest neighbor. These peepholes can fail the pattern match - // on a node some distance away, and if that node ever changes we should - // 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 - * or output of this node, that is, it is at least one step away. The node - * being added must benefit from this node being peepholed. - */ - Node addDep( Node dep ) { - return addDep(dep,false); - } - - // A bulk Phi inspects its users' partitions. Def-to-use propagation does - // not revisit that producer when an immediate user's partition changes. - Node addDepForwards(Node dep) { return addDep(dep,true); } - - private Node addDep(Node dep, boolean forwards) { - // 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( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( !forwards && _outputs.find(dep) != -1 ) return this; - _deps.add(dep); - return this; - } - - // Move the dependents onto a worklist, and clear for future dependents. - public void moveDepsToWorklist( ) { - if( _deps==null ) return; - IterPeeps.addAll(_deps); - _deps.clear(); - } - - // Global Value Numbering. Hash over opcode and inputs; hits in this table - // are structurally equal. - public static final HashMap GVN = new HashMap<>(); - - // Two nodes are equal if they have the same inputs and the same "opcode" - // which means the same Java class, plus same internal parts. - @Override public final boolean equals( Object o ) { - if( o==this ) return true; - if( o.getClass() != getClass() ) return false; - Node n = (Node)o; - int len = _inputs.size(); - if( len != n._inputs.size() ) return false; - for( int i=0; i>13) ^ n._nid; - if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing - return (_hash=hash); - } - // Subclasses add extra hash info (such as ConstantNodes constant) - int hash() { return 0; } - - // ------------------------------------------------------------------------ - // - - /** Is this Node Memory related */ - public boolean isMem() { return false; } - - // Semantic change to the graph (so NOT a peephole), used by the Parser. - // If any input is a float, flip to a float-flavored opcode and widen any - // non-float input. - public final Node widen() { - if( !hasFloatInput() ) return this; - Node flt = copyF(); - if( flt==null ) return this; - for( int i=1; i(Node.class); - n._outputs = new Ary<>(Node.class); - n._deps = null; - n._hash = 0; - return n; - } - - // Report any post-optimize errors - String err() { return null; } - - /** - * Used to allow repeating tests in the same JVM. This just resets the - * Node unique id generator, and is done as part of making a new Parser. - */ - public static void reset() { - UNIQUE_ID = 1; - GVN.clear(); - ITER_CNT = ITER_NOP_CNT = 0; - } - - // Utility to walk the entire graph applying a function; return the first - // not-null result. - private static final BitSet WVISIT = new BitSet(); - final public E walk( Function pred ) { - assert WVISIT.isEmpty(); - E rez = _walk(pred); - WVISIT.clear(); - return rez; - } - - private E _walk( Function pred ) { - if( WVISIT.get(_nid) ) return null; // Been there, done that - WVISIT.set(_nid); - E x = pred.apply(this); - if( x != null ) return x; - for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; - for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; - return null; - } - - /** - * Debugging utility to find a Node by index - */ - public Node find(int nid) { - return walk( n -> n._nid==nid ? n : null ); - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java deleted file mode 100644 index b9cf4904..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.*; - -public class NotNode extends Node { - public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } - - @Override protected String format() { return "(!%1)"; } - - @Override - public Type compute() { - Type t0 = in(1)._type; - if( t0.isHigh() ) return TypeInteger.BOOL.dual(); - switch( t0 ) { - case TypeInteger i0: return i0._max < 0 || i0._min > 0 ? TypeInteger.FALSE : (i0==TypeInteger.ZERO ? TypeInteger.TRUE : TypeInteger.BOOL); - case TypeFloat i0: - return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : TypeInteger.BOOL; - case TypeMemPtr p0: - // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot - // If input in null then true - // If input is not null ptr then false - if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.TRUE; - if( !p0._nil ) return TypeInteger.FALSE; - return TypeInteger.BOOL; - case Type t: - if( t0.getClass() != Type.class ) - // Only doing NOT on ints and ptrs - throw Utils.TODO(); - return TypeInteger.BOOL; - } - } - - @Override - public Node idealize() { return null; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java deleted file mode 100644 index 5be31b88..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class OrNode extends Node { - public OrNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Or"; } - - @Override protected String format() { return "(%1|%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if( i0.isConstant() && i1.isConstant() ) - return TypeInteger.constant(i0.value()|i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Or of 0. We do not check for (0|x) because this will already - // canonicalize to (x|0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x | (phi cons)) | con) ? - // Do we have ((x | (phi cons)) | (phi cons)) ? - // Push constant up through the phi: x | (phi con0|con0 con1|con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new OrNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '|' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '|' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/OutNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/OutNode.java deleted file mode 100644 index dbac136b..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/OutNode.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Ary; - -public interface OutNode { - Ary outs(); -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java deleted file mode 100644 index 942748e7..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.Utils; - - -public class PhiNode extends Node { - - final String _label; - - // The Phi type we compute must stay within the domain of the Phi. - // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - final Type _declaredType; - - public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } - - public static PhiNode make(String label, Type type, Node... inputs) { - return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); - } - - @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - p.p("Z"); - p.p("Phi("); - for( Node in : _inputs ) p.n(in).p(","); - return p.unchar(',').close(); - } - - public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMultiTail() { return true; } - @Override public boolean isMem() { return _declaredType instanceof TypeMem; } - - @Override - public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() ) return _declaredType; - // Set type to local top of the starting type - Type t = _declaredType.lub(); - for (int i = 1; i < nIns(); i++) - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - if( r.in(i).addDep(this)._type != Type.XCONTROL ) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( !(region() instanceof RegionNode r ) ) - return in(1); // Input has collapse to e.g. starting control. - if( r.inProgress() || r.nIns()<=1 ) - return null; // Input is in-progress - - // If we have only a single unique input, become it. - Node live = singleUniqueInput(); - if (live != null) - return live; - - // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). - Node down; - if( same_op() && (down=drop_same_op())!=null ) return down; - - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. - if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); - } - // If merging a null-checked null and the checked value, just use the value. - // if( val ) ..; phi(Region,False=0/null,True=val); - // then replace with plain val. - if( nIns()==3 ) { - int nullx = -1; - if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; - if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; - if( nullx != -1 ) { - Node val = in(3-nullx); - if( r.idom(this).addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) { - // Must walk the idom on the null side to make sure we hit False. - CFGNode idom = (CFGNode)r.in(nullx); - while( idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof CProjNode proj && proj._idx==1 ) - return val; - } - } - } - - return null; - } - - private boolean same_op() { - Node op = in(1); - if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || - op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; - // A bulk Phi must split its aliases before factoring precise Stores. - if( this instanceof BulkMemPhiNode ) return false; - // Bulk splitting currently identifies parallel slices by Region/alias. - // Factoring can introduce another slice at a different memory point; - // wait until that Region's bulk partitioning has finished. - if( op instanceof MemOpNode ) - for( Node use : region()._outputs ) - if( use instanceof BulkMemPhiNode ) { - use.addDepForwards(this); - return false; - } - Node busy = null; - for( int i=1; i1 ) { - for( Node use : n._outputs ) - if( use!=null && use!=this ) use.addDepForwards(this); - if( n instanceof StoreNode || busy!=null ) return false; - busy=n; - } - for( int j=1; j _print1(ExprPrinter p) { return p.p(_label); } - - @Override public CFGNode cfg0() { return in(0).cfg0(); } - - @Override public boolean isMultiTail() { return in(0).isMultiHead(); } - @Override public boolean isMem() { return _type instanceof TypeMem; } - - @Override - public Type compute() { - Type t = in(0)._type; - return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; - } - - @Override public Node idealize() { return null; } - - @Override - boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } - - @Override - int hash() { return _idx; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java deleted file mode 100644 index fa4e0242..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; - - -/** - * Cast a pointer to read-only - */ -public class ReadOnlyNode extends Node { - public ReadOnlyNode( Node n ) { super(null,n); } - @Override public String label() { return "ReadOnly"; } - - @Override protected String format() { return "(const)%1"; } - @Override public Type compute() { - Type t = in(1)._type; - return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t; - } - - @Override public Node idealize() { - if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() ) - return in(1); - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java deleted file mode 100644 index 27866139..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; - -public class RegionNode extends CFGNode { - - public RegionNode(Node... nodes) { super(nodes); } - - @Override - public String label() { return "Region"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()); - } - - @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } - - @Override - public Type compute() { - if( inProgress() ) return Type.CONTROL; - Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) - t = t.meet(in(i)._type); - return t; - } - - @Override - public Node idealize() { - if( inProgress() ) return null; - // Delete dead paths into a Region - int path = findDeadInput(); - if( path != 0 && - // Do not delete the entry path of a loop (ok to remove the back - // edge and make the loop a single-entry Region which folds away - // the Loop). Folding the entry path confused the loop structure, - // moving the backedge to the entry point. - !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { - // Cannot use the obvious output iterator here, because a Phi - // deleting an input might recursively delete *itself*. This - // shuffles the output array, and we might miss iterating an - // unrelated Phi. So on rare occasions we repeat the loop to get - // all the Phis. - int nouts = 0; - while( nouts != nOuts() ) { - nouts = nOuts(); - for( int i=0; i1 && in(nIns()-1) == null; - } - - // Never equal if inProgress - @Override boolean eq( Node n ) { - return !inProgress(); - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index 6c773c42..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - - -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ -public class ReturnNode extends CFGNode { - - public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // A synthetic never-taken loop exit has no source scope. - super(ctrl, scope == null ? null : scope.mem(), data); - } - - public Node ctrl() { return in(0); } - public Node mem () { return in(1); } - public Node expr() { return in(2); } - - @Override - public String label() { return "Return"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("return "); - p.n(expr()); - return p.p(";"); - } - - @Override - public Type compute() { - // Return exposes the complete memory state. - return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); - } - - @Override - public Node idealize() { - if( ctrl()._type==Type.XCONTROL ) - return ctrl(); - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java deleted file mode 100644 index 78e7698c..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class RoundF32Node extends Node { - public RoundF32Node(Node lhs) { super(null, lhs); } - - @Override public String label() { return "RoundF32"; } - - @Override protected String format() { return "((f32)%1)"; } - - @Override - public Type compute() { - if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() ) - return TypeFloat.constant((float)i0.value()); - return in(1)._type; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Type t1 = lhs._type; - - // RoundF32 of float - if( t1 instanceof TypeFloat tf && tf._sz==32 ) - return lhs; - - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java deleted file mode 100644 index 44233b3d..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class SarNode extends Node { - public SarNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Sar"; } - - @Override protected String format() { return "(%1>>%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1) { - if( i0 == TypeInteger.ZERO ) - return TypeInteger.ZERO; - if( i0.isConstant() && i1.isConstant() ) - return TypeInteger.constant(i0.value()>>i1.value()); - if( i1.isConstant() ) { - int log = (int)i1.value(); - return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1); - } - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Sar of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 ) - return lhs; - - // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SarNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '>>' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '>>' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java deleted file mode 100644 index b539ff87..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ /dev/null @@ -1,407 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Ary; -import com.seaofnodes.simple.type.*; -import java.util.*; - -/** - * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variable records. - */ -public class ScopeNode extends Node { - - /** - * The control is a name that binds to the currently active control - * node in the graph - */ - public static final String CTRL = "$ctrl"; - public static final String ARG0 = "arg"; - public static final String MEM0 = "$mem"; - - /** The tracked fields are now complex enough to deserve a array-of-structs layout - */ - public static class Var { - public final int _idx; // index in containing scope - public final String _name; // Declared name - private Type _type; // Declared type - public final boolean _final; // Final field - public Var(int idx, String name, Type type, boolean xfinal) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - } - public Type type() { - if( !_type.isFRef() ) return _type; - // Update self to no longer use the forward ref type - Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); - return (_type=_type.meet(def)); - } - public Type lazyGLB() { - Type t = type(); - return t instanceof TypeMemPtr ? t : t.glb(); - } - @Override public String toString() { - return _type.toString()+(_final ? " ": " !")+_name; - } - } - - public Node in(Var v) { return in(v._idx); } - - @Override public Type compute() { return Type.BOTTOM; } - @Override public Node idealize() { return null; } - - // All active/live variables in all nested scopes, all run together - public final Ary _vars; - - // Size of each nested lexical scope - public final Ary _lexSize; - - // True if parsing inside of a constructor - public final Ary _inCons; - - // Extra guards; tested predicates and casted results - private final Ary _guards; - - // A new ScopeNode - public ScopeNode() { - _type = Type.BOTTOM; - _vars = new Ary<>(Var .class); - _lexSize= new Ary<>(Integer.class); - _inCons = new Ary<>(Boolean.class); - _guards = new Ary<>(Node .class); - } - - @Override public String label() { return "Scope"; } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - p.p("Scope[ "); - int j=1; - for( int i=0; i N ctrl(N n) { return setDef(0,n); } - - public void push() { push(false); } - public void push(boolean inCon) { - assert _lexSize._len==_inCons._len; - _lexSize.push(_vars.size()); - _inCons.push(inCon); - } - public void pop() { - assert _lexSize._len==_inCons._len; - int n = _lexSize.pop(); - _inCons.pop(); - popUntil(n); - _vars.setLen(n); - } - - public boolean inCon() { return _inCons.last(); } - - // Find name in reverse, return an index into _vars or -1. Linear scan - // instead of hashtable, but probably doesn't matter until the scan - // typically hits many dozens of variables. - int find( String name ) { - for( int i=_vars.size()-1; i>=0; i-- ) - if( _vars.get(i)._name.equals(name) ) - return i; - return -1; - } - - /** - * Create a new variable name in the current scope - */ - public boolean define( String name, Type declaredType, boolean xfinal, Node init ) { - assert name.charAt(0)!='$' || _lexSize.size()==1; // Later scopes do not define memory - if( _lexSize._len > 1 ) - for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) - if( _vars.get(i)._name.equals(name) ) - return false; // Double define - _vars.add(new Var(nIns(),name,declaredType,xfinal)); - addDef(init); - return true; - } - - /** - * Lookup a name in all scopes starting from most deeply nested. - * - * @param name Name to be looked up - * @return null if not found, or the implementing Node - */ - public Var lookup( String name ) { - int idx = find(name); - // -1 is missed in all scopes, not found - return idx == -1 ? null : update(_vars.at(idx),null); - } - - /** - * If the name is present in any scope, then redefine else null - * - * @param name Name being redefined - * @param n The node to bind to the name - */ - public void update( String name, Node n ) { - int idx = find(name); - assert idx>=0; - update(_vars.at(idx),n); - } - - public Var update( ScopeNode.Var v, Node st ) { - Node old = in(v._idx); - if( old instanceof ScopeNode loop ) { - // Lazy Phi! - Node def = loop.in(v._idx); - old = def instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? def - // Set real Phi in the loop head - // The phi takes its one input (no backedge yet) from a recursive - // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); - setDef(v._idx,old); - } - if( st!=null ) setDef(v._idx,st); // Set new value - return v; - } - - /** - * Duplicate a ScopeNode; including all levels, up to Nodes. So this is - * neither shallow (would dup the Scope but not the internal HashMap - * tables), nor deep (would dup the Scope, the HashMap tables, but then - * also the program Nodes). - *

- * If the {@code loop} flag is set, the edges are filled in as the original - * Scope, as an indication of Lazy Phis at loop heads. The goal here is to - * not make Phis at loop heads for variables which are never touched in the - * loop body. - *

- * The new Scope is a full-fledged Node with proper use<->def edges. - */ - public ScopeNode dup() { return dup(false); } - public ScopeNode dup(boolean loop) { - ScopeNode dup = new ScopeNode(); - // Our goals are: - // 1) duplicate the name bindings of the ScopeNode across all stack levels - // 2) Make the new ScopeNode a user of all the nodes bound - // 3) Ensure that the order of defs is the same to allow easy merging - dup._vars .addAll(_vars ); - dup._lexSize.addAll(_lexSize); - dup._inCons .addAll(_inCons ); - dup._guards .addAll(_guards ); - // The dup'd guards all need dup'd keepers, to keep proper accounting - // when later removing all guards - for( Node n : _guards ) - if( !(n instanceof CFGNode) ) - n.keep(); - dup.addDef(ctrl()); // Control input is just copied - - // Memory is one binding, with the same lazy loop Phi as scalar names. - for( int i=1; i 0 ) { - Node cast = _guards.at(--i); - if( cast instanceof CFGNode ) continue; // Marker between guard sets - Node xpred = _guards.at(--i); - if( xpred == pred ) - return cast; - } - return pred; - } - - // Kill guards also - @Override public void kill() { - for( Node n : _guards ) - if( !(n instanceof CFGNode) ) - n.unkill(); - _guards.clear(); - super.kill(); - } - - - private Node replace( Node old, Node cast ) { - assert old!=null && old!=cast; - for( int i=0; i (x << i) + (c << i) - if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() && c!=TypeInteger.ZERO ) { - long sum = c.value() << shl.value(); - if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) - return new AddNode( new ShlNode(add.in(1),rhs).peephole(), Parser.con(sum) ); - } - } - - // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5) - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new ShlNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '<<' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '<<' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java deleted file mode 100644 index 757fd69e..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class ShrNode extends Node { - public ShrNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Shr"; } - - @Override protected String format() { return "(%1>>>%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if (t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if( i0 == TypeInteger.ZERO ) - return TypeInteger.ZERO; - if( i0.isConstant() && i1.isConstant() ) - return TypeInteger.constant(i0.value()>>>i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t2 = rhs._type; - - // Shr of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 ) - return lhs; - - // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java deleted file mode 100644 index cefa2d72..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; -import java.util.HashSet; -import static com.seaofnodes.simple.Utils.TODO; - -/** Start supplies control, whole memory, and the argument, in that order. */ -public class StartNode extends LoopNode implements MultiNode { - - final Type _arg; - - public StartNode(Type arg) { super(null); _arg = arg; _type = compute(); } - - @Override public String label() { return "Start"; } - - @Override protected ExprPrinter _print1(ExprPrinter p) { - return p.p(label()); - } - - @Override public boolean isMultiHead() { return true; } - @Override public boolean blockHead() { return true; } - @Override public CFGNode cfg0() { return this; } - - @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); - } - - @Override public Node idealize() { return null; } - - // No immediate dominator, and idepth==0 - @Override public int idepth() { return 0; } - @Override public CFGNode idom(Node dep) { return null; } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java deleted file mode 100644 index d0eb5750..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.GlobalCodeMotion; - - -public class StopNode extends CFGNode { - - public final String _src; - - public StopNode(String src) { - super(); - _src = src; - } - - @Override - public String label() { - return "Stop"; - } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( ret()!=null ) return p.n(ret()); - p.p("Stop[ "); - for( Node ret : _inputs ) - p.n(ret).p(" "); - return p.p("]"); - } - - @Override public boolean blockHead() { return true; } - - // If a single Return, return it. - // Otherwise, null because ambiguous. - public ReturnNode ret() { - return nIns()==1 ? (ReturnNode)in(0) : null; - } - - @Override - public Type compute() { - return Type.BOTTOM; - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i { - String msg = n.err(); - if( msg != null && obs != null ) obs.error(n, msg); - return msg; - }); - if( err != null ) throw new RuntimeException(err); - if( obs != null ) obs.phase("TypeCheck"); - return this; - } - StopNode GCM() { - Parser.START.buildLoopTree(this); - - 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 deleted file mode 100644 index 48c8927f..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.*; - - -/** - * Store represents setting a value to a memory based object, in chapter 10 - * this means a field inside a struct. - */ -public class StoreNode extends MemOpNode { - - private final boolean _init; // Initializing writes are allowed to write null - - /** - * @param name The struct field we are assigning to - * @param mem The memory alias node - this is updated after a Store - * @param ptr The ptr to the struct base where we will store a value - * @param off The offset inside the struct base - * @param value Value to be stored - */ - public StoreNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(name, alias, glb, mem, ptr, off, value); - _init = init; - } - - // Debugger label - @Override boolean canDrop(MemOpNode other, Node dep) { - return super.canDrop(other,dep) && _init==((StoreNode)other)._init; - } - - @Override public String label() { return "st_"+mlabel(); } - @Override public boolean isMem() { return true; } - - public Node val() { return in(4); } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - return p.p(".").p(_name).p("=").p( val()).p(";"); - } - - @Override - public Type compute() { - Type val = val()._type; - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); - return TypeMem.make(_alias,t); - } - - @Override - public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,IterPeeps.add(merge.alias(_alias))); - return this; - } - - - // Simple store-after-store on same address. Should pick up the - // required init-store being stomped by a first user store. - if( mem() instanceof StoreNode st && - ptr()==st.ptr() && _alias==st._alias && // Must check same object - off()==st.off() && // And same offset - ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - checkOnlyUse(st) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect - setDef(1,st.mem()); - return this; - } - - // Simple store-after-new on same address. Should pick up the - // an init-store being stomped by a first user store. - if( mem() instanceof ProjNode st && st .in(0) instanceof NewNode nnn && - ptr() instanceof ProjNode ptr && ptr.in(0) == nnn && - ptr()._type instanceof TypeMemPtr tmp && // No bother if weird dead pointers - // Cannot fold a store of a single element over the array body initializer value - !(tmp._obj.isAry() && tmp._obj._fields[1]._alias==_alias) && - // Very sad strong cutout: val has to be legal to hoist to a New - // input, which means it cannot depend on the New. Many many - // things are legal here but difficult to check without doing a - // full dominator check. Example failure: - // "struct C { C? c; }; C self = new C { c=self; }" - val()._type.isHighOrConst() && - // Must have exactly one use of "this" or you get weird - // non-serializable memory effects in the worse case. - checkOnlyUse(st) && - // Folding away a broken store - err()==null ) { - nnn.setDef(nnn.findAlias(_alias),val()); - // Must retype the NewNode - nnn ._type = nnn. compute(); - mem()._type = mem().compute(); - return st; - } - - return null; - } - - // Check that "mem" has no uses except "this" - private boolean checkOnlyUse(Node mem) { - if( mem.nOuts()==1 ) return true; - // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that - // when the other uses go away we can retry. - for( Node use : mem._outputs ) - if( use != this ) - use.addDep(this); - return false; - } - - @Override - String err() { - String err = super.err(); - if( err != null ) return err; - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( tmp._obj.field(_name)._final ) - return "Cannot modify final field '"+_name+"'"; - Type t = val()._type; - return _init || t.isa(_declaredType) ? null : "Cannot store "+t+" into field "+_declaredType+" "+_name; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StructNode.java deleted file mode 100644 index 429b49cc..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; - -/** - * Build a compound object - */ -public class StructNode extends Node { - - public TypeStruct _ts; - - @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } - - @Override - protected ExprPrinter _print1(ExprPrinter p) { - if( _ts==null ) return p.p("STRUCT?"); - p.p(_ts._name).p(" {"); - for( int i=0; i _print1(ExprPrinter p) { return p.p("Xctrl"); } - @Override public Type compute() { return Type.XCONTROL; } - @Override public Node idealize() { return null; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java deleted file mode 100644 index ef7273b8..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; - - -public class XorNode extends Node { - public XorNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override public String label() { return "Xor"; } - - @Override protected String format() { return "(%1^%2)"; } - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger i0 && - t2 instanceof TypeInteger i1 ) { - if( i0.isConstant() && i1.isConstant() ) - return TypeInteger.constant(i0.value()^i1.value()); - } - return TypeInteger.BOT; - } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Xor of 0. We do not check for (0^x) because this will already - // canonicalize to (x^0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - // Do we have ((x ^ (phi cons)) ^ con) ? - // Do we have ((x ^ (phi cons)) ^ (phi cons)) ? - // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...) - Node phicon = AddNode.phiCon(this,true); - if( phicon!=null ) return phicon; - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new XorNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '^' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '^' " + in(2)._type; - return null; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java deleted file mode 100644 index 7ba58e6f..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.graph.GraphAdapter; -import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMem; - -/** Chapter 17's view of the IR; browser, transport and layout are shared. */ -public class SimpleGraphAdapter extends GraphAdapter { - - @Override protected String edgeName(Node n, int i) { - return n instanceof ScopeNode scope && i < scope._vars.size() - ? scope._vars.get(i)._name : null; - } - @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } - - @Override protected Kind kind(Node n) { - if( n instanceof StopNode ) return Kind.STOP; - 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; - } - - @Override protected 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 || n instanceof NewNode) ) 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 deleted file mode 100644 index d774967b..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ /dev/null @@ -1,45 +0,0 @@ -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); - _phase = "TypeCheck"; - _parser.STOP.typeCheck(); - } - @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 Node scope() { return _parser._scope; } - @Override protected void roots(ArrayList roots) { - roots.add(Parser.START); - roots.add(_parser.STOP); - roots.addAll(_parser._xScopes); - roots.add(_ret); - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java deleted file mode 100644 index 349c1fb0..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a field in a struct. This is not a Type in the type system. - */ -public class Field extends Type { - - // The pair {fieldName,type} uniquely identifies a field. - - // Field name - public final String _fname; - // Type of the field - public final Type _type; - // Unique memory alias, not sensibly part of a "type" but very convenient here. - public final int _alias; - // Field must be written to exactly once, no more, no less - public final boolean _final; - - private Field(String fname, Type type, int alias, boolean xfinal ) { - super(TFLD); - _fname = fname; - _type = type; - _alias = alias; - _final = xfinal; - } - // Make with existing alias - public static Field make( String fname, Type type, int alias, boolean xfinal ) { - return new Field(fname,type,alias,xfinal).intern(); - } - public Field makeFrom( Type type ) { - return type == _type ? this : new Field(_fname,type,_alias,_final).intern(); - } - @Override public Field makeRO() { return _final ? this : make(_fname,_type.makeRO(),_alias,true); } - @Override public boolean isFinal() { return _final && _type.isFinal(); } - - public static final Field TEST = make("test",TypeInteger.ZERO,-2,false); - public static void gather(ArrayList ts) { ts.add(TEST); } - - @Override Field xmeet( Type that ) { - Field fld = (Field)that; // Invariant - assert _fname.equals(fld._fname); - assert _alias==fld._alias; - return make(_fname,_type.meet(fld._type),_alias,_final | fld._final); - } - - @Override - public Field dual() { return make(_fname,_type.dual(),_alias,!_final); } - - @Override public Field glb() { - Type glb = _type.glb(); - return glb==_type ? this : make(_fname,glb,_alias,_final); - } - - @Override public Field lub() { - Type lub = _type.lub(); - return lub==_type ? this : make(_fname,lub,_alias,_final); - } - - // Override in subclasses - int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias ^ (_final ? 1024 : 0); } - - boolean eq(Type t) { - Field f = (Field)t; - return _fname.equals(f._fname) && _type==f._type && _alias==f._alias && _final==f._final; - } - - - @Override - public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_final?"":"!").append(_fname).append(":").append(_alias).append(" : ")); - } - - @Override public String str() { return _fname; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java deleted file mode 100644 index 4ebb2b6d..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * These types are part of a Monotone Analysis Framework, - * @see see for example this set of slides. - *

- * The types form a lattice; @see a symmetric complete bounded (ranked) lattice. - *

- * This wild lattice theory will be needed later to allow us to easily beef up - * the analysis and optimization of the Simple compiler... but we don't need it - * now, just know that it is coming along in a later Chapter. - *

g - * One of the fun things here is that while the theory is deep and subtle, the - * actual implementation is darn near trivial and is generally really obvious - * what we're doing with it. Right now, it's just simple integer math to do - * simple constant folding e.g. 1+2 == 3 stuff. - */ - -public class Type { - static final HashMap INTERN = new HashMap<>(); - - // ---------------------------------------------------------- - // Simple types are implemented fully here. "Simple" means: the code and - // type hierarchy are simple, not that the Type is conceptually simple. - static final byte TBOT = 0; // Bottom (ALL) - static final byte TTOP = 1; // Top (ANY) - static final byte TCTRL = 2; // Ctrl flow bottom - static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) - static final byte TSIMPLE = 4; // End of the Simple Types - static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TFLT = 6; // All Integers; see TypeInteger - static final byte TTUPLE = 7; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 8; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TMEMPTR = 9; // Memory pointer type - static final byte TSTRUCT =10; // Structs; tuples with named fields - static final byte TFLD =11; // Fields into struct - static final byte TARRAY =12; // Array - - public final byte _type; - - public boolean is_simple() { return _type < TSIMPLE; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; - protected Type(byte type) { _type = type; } - - public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL - public static final Type TOP = new Type( TTOP ).intern(); // ANY - public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl - public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl - public static Type[] gather() { - ArrayList ts = new ArrayList<>(); - ts.add(BOTTOM); - ts.add(CONTROL); - Field.gather(ts); - TypeInteger.gather(ts); - TypeFloat.gather(ts); - TypeMem.gather(ts); - TypeMemPtr.gather(ts); - TypeStruct.gather(ts); - TypeTuple.gather(ts); - int sz = ts.size(); - for( int i = 0; i < sz; i++ ) - ts.add(ts.get(i).dual()); - return ts.toArray(new Type[ts.size()]); - } - - // Is high or on the lattice centerline. - public boolean isHigh () { return _type==TTOP || _type==TXCTRL; } - public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } - - // Strict constant values, things on the lattice centerline. - // Excludes both high and low values - public boolean isConstant() { return false; } - - /** - * Display Type name in a format that's good for IR printer - */ - public StringBuilder typeName( StringBuilder sb) { return print(sb); } - - - // ---------------------------------------------------------- - - // Notes on Type interning: At the moment it is not easy to reset the - // interned types because we hold static references to several types and - // these are scattered around. This means the INTERN cache will retain all - // types from every run of the Parser. For this to work correctly types - // must be rigorous about defining when they are the same. Also types need - // to be immutable once defined. The rationale for interning is - // *correctness* with cyclic type definitions. Simple structural recursive - // checks go exponential with merely sharing, but with cycles they will - // stack overflow and crash. Intering means we do not need to have sharing - // checks with every type compare, only during interning. - - // Factory method which interns "this" - public T intern() { - T nnn = (T)INTERN.get(this); - if( nnn==null ) - INTERN.put(nnn=(T)this,this); - return nnn; - } - - private int _hash; // Hash cache; not-zero when set. - @Override - public final int hashCode() { - if( _hash!=0 ) return _hash; - _hash = hash(); - if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing - return _hash; - } - // Override in subclasses - int hash() { return _type; } - - @Override - public final boolean equals( Object o ) { - if( o==this ) return true; - if( !(o instanceof Type t)) return false; - if( _type != t._type ) return false; - return eq(t); - } - // Overridden in subclasses; subclass can assume "this!=t" and java classes are same - boolean eq(Type t) { return true; } - - - // ---------------------------------------------------------- - public final Type meet(Type t) { - // Shortcut for the self case - if( t == this ) return this; - // Same-type is always safe in the subclasses - if( _type==t._type ) return xmeet(t); - // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". - if( is_simple() ) return this.xmeet(t ); - if( t.is_simple() ) return t .xmeet(this); - return Type.BOTTOM; // Mixing 2 unrelated types - } - - // Compute meet right now. Overridden in subclasses. - // Handle cases where 'this.is_simple()' and unequal to 't'. - // Subclassed xmeet calls can assert that '!t.is_simple()'. - Type xmeet(Type t) { - assert is_simple(); // Should be overridden in subclass - // ANY meet anything is thing; thing meet ALL is ALL - if( _type==TBOT || t._type==TTOP ) return this; - if( _type==TTOP || t._type==TBOT ) return t; - // 'this' is {TCTRL,TXCTRL} - if( !t.is_simple() ) return BOTTOM; - // 't' is {TCTRL,TXCTRL} - return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; - } - - public Type dual() { - return switch( _type ) { - case TBOT -> TOP; - case TTOP -> BOTTOM; - case TCTRL -> XCONTROL; - case TXCTRL -> CONTROL; - default -> throw Utils.TODO(); // Should not reach here - }; - } - - // ---------------------------------------------------------- - // Our lattice is defined with a MEET and a DUAL. - // JOIN is dual of meet of both duals. - public final Type join(Type t) { - if( this==t ) return this; - return dual().meet(t.dual()).dual(); - } - - // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } - - /** Compute greatest lower bound in the lattice */ - public Type glb() { return _type==TCTRL ? CONTROL : BOTTOM; } - /** Compute least upper bound in the lattice */ - public Type lub() { return _type==TCTRL ? XCONTROL : TOP; } - - // Make an initial/default version of this type. Typically, 0 for integers - // and null for nullable pointers. - public Type makeInit() { return this; } - // Make a zero version of this type, 0 for integers and null for pointers. - public Type makeZero() { return null; } - // Make a non-zero version of this type, if possible. Integers attempt to - // exclude zero from their range and pointers become not-null. - public Type nonZero() { return glb(); } - - // Is forward-reference - public boolean isFRef() { return false; } - - // All reachable struct Fields are final - public boolean isFinal() { return true; } - - // Make all reachable struct Fields final - public Type makeRO() { return this; } - - // ---------------------------------------------------------- - - // Size in bits to hold an instance of this type. - // Sizes are expected to be between 1 and 64 bits. - // Size 0 means this either takes no space (such as a known-zero field) - // or isn't a scalar to be stored in memory. - public int log_size() { throw Utils.TODO(); } - - // ---------------------------------------------------------- - // Useful in the debugger, which calls toString everywhere. - // This is a more verbose dev-friendly print. - @Override - public final String toString() { - return print(new StringBuilder()).toString(); - } - - public StringBuilder print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} - - // This is used by error messages, and is a shorted print. - public String str() { return STRS[_type]; } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java deleted file mode 100644 index f85abbed..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Float Type - */ -public class TypeFloat extends Type { - - public final static TypeFloat TOP = make((byte)-64, 0); - public final static TypeFloat T32 = make((byte)-32, 0); - public final static TypeFloat ZERO= make((byte) 0, 0); - public final static TypeFloat B32 = make((byte) 32, 0); - public final static TypeFloat BOT = make((byte) 64, 0); - - /** +/-64 for double; +/-32 for float, or 0 for constants */ - public final byte _sz; - - /** - * The constant value or 0 - */ - public final double _con; - - private TypeFloat(byte sz, double con) { - super(TFLT); - _sz = sz ; - _con = con; - } - private static TypeFloat make(byte sz, double con) { - return new TypeFloat(sz,con).intern(); - } - - public static TypeFloat constant(double con) { return make((byte)0, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); ts.add(B32); ts.add(constant(3.141592653589793)); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==T32 ) return sb.append("F32Top"); - if( this==B32 ) return sb.append("F32Bot"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(_con); - } - - @Override public String str() { - if( this==TOP ) return "~flt"; - if( this==T32 ) return "~f32"; - if( this==B32 ) return "f32"; - if( this==BOT ) return "flt"; - return ""+_con+(isF32() ? "f" : ""); - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==T32 ) return sb.append("F32Top"); - if( this==B32 ) return sb.append("F32Bot"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(isF32() ? "F32" : "Flt"); - } - - boolean isF32() { return ((float)_con)==_con; } - @Override public boolean isHigh() { return _sz< 0; } - @Override public boolean isHighOrConst() { return _sz<=0; } - @Override public boolean isConstant() { return _sz==0; } - - @Override public int log_size() { - int sz = _sz==0 ? (isF32() ? 32 : 64) : Math.abs(_sz); - return _sz==32 ? 2 : 3; - } - - public double value() { return _con; } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeFloat) - TypeFloat i = (TypeFloat)other; // Contract - // Larger size in i1, smaller in i0 - TypeFloat i0 = _sz < i._sz ? this : i; - TypeFloat i1 = _sz < i._sz ? i : this; - - if( i1._sz== 64 ) return BOT; - if( i0._sz==-64 ) return i1; - if( i1._sz== 32 ) - return i0._sz==0 && !i0.isF32() ? BOT : B32; - if( i1._sz!= 0 ) return i1; - // i1 is a constant - if( i0._sz==-32 ) - return i1.isF32() ? i1 : BOT; - // Since both are constants, and are never equals (contract) unequals - // constants fall to bottom - return i0.isF32() && i1.isF32() ? B32 : BOT; - } - - @Override - public Type dual() { - if( _sz==0 ) return this; // Constants are a self-dual - return make((byte)-_sz,0); - } - - @Override public Type glb() { return BOT; } - @Override public Type lub() { return TOP; } - @Override public TypeFloat makeInit() { return ZERO; } - @Override public TypeFloat makeZero() { return ZERO; } - - @Override - int hash() { return (int)(Double.hashCode(_con) ^ _sz); } - @Override - public boolean eq( Type t ) { - TypeFloat i = (TypeFloat)t; // Contract - return _con==i._con && _sz==i._sz; - } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java deleted file mode 100644 index b2979d7b..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Integer Type - */ -public class TypeInteger extends Type { - - public final static TypeInteger TOP = make(false, 0); - public final static TypeInteger BOT = make(false, 1); - public final static TypeInteger ZERO= make(0,0); - public final static TypeInteger U1 = make(0,1); - public final static TypeInteger BOOL= U1; - public final static TypeInteger FALSE=ZERO; - public final static TypeInteger TRUE= make(1,1); - public final static TypeInteger I8 = make(-128,127); - public final static TypeInteger I16 = make(-32768,32767); - public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1); - - public final static TypeInteger U8 = make(0,255); - public final static TypeInteger U16 = make(0,65535); - public final static TypeInteger U32 = make(0,(1L<<32)-1); - - /** - * Describes an integer *range* - everything from min to max; both min and - * max are inclusive. If min==max, this is a constant. - * - * If min <= max, this is a below center (towards bottom). - * If min > max, this is an above center (towards top). - */ - public final long _min, _max; - - private TypeInteger(long min, long max) { super(TINT); _min = min; _max = max; } - public static TypeInteger make(long lo, long hi) { return new TypeInteger(lo,hi).intern(); } - public static TypeInteger make(boolean is_con, long con) { - return make(is_con ? con : (con==0 ? Long.MAX_VALUE : Long.MIN_VALUE), - is_con ? con : (con==0 ? Long.MIN_VALUE : Long.MAX_VALUE)); - } - - public static TypeInteger constant(long con) { return make(true, con); } - - public static void gather(ArrayList ts) { ts.add(ZERO); ts.add(BOT); ts.add(U1); ts.add(U8); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { return sb.append(str()); } - - @Override public String str() { - if( this==TOP ) return "~int"; - if( this==BOT ) return "int"; - if( this==BOOL) return ("bool"); - if( this==I8 ) return ("i8"); - if( this==I16 ) return ("i16"); - if( this==I32 ) return ("i32"); - if( this==U8 ) return ("u8"); - if( this==U16 ) return ("u16"); - if( this==U32 ) return ("u32"); - if( isConstant() ) return ""+_min; - return "["+_min+"-"+_max+"]"; - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append("Int"); - } - - @Override public boolean isHigh () { return _min > _max; } - @Override public boolean isHighOrConst() { return _min >= _max; } - @Override public boolean isConstant () { return _min == _max; } - - @Override public int log_size() { - if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes - if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes - if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes - if( this==BOT ) return 3; // 1<<3 == 8 bytes - if( isHighOrConst() ) return 0; - throw Utils.TODO(); - } - - public long value() { assert isConstant(); return _min; } - - // AND-mask of forced zeros. e.g. unsigned types will return their mask; - // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return - // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set. - public long mask() { - if( isHigh() ) return 0; - if( isConstant() ) return _min; - //if( _min<0 ) return -1L; - //if( _max==Long.MAX_VALUE ) return -1L; - // Those bit positions which differ min to max - long x = _min ^ _max; - // Highest '1' bit in the differ set. Since the range is from min to - // max, all values below here are possible. - long ff1 = Long.highestOneBit(x); - // Make a all-1's mask from ff1, and set over the same bits (either min - // or max is ok). - long mask = _min | (ff1-1) | ff1; - return mask; - } - - @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeInteger) - TypeInteger i = (TypeInteger)other; // Contract - return make(Math.min(_min,i._min), Math.max(_max,i._max)); - } - - @Override public TypeInteger dual() { return make(_max,_min); } - @Override public TypeInteger glb() { return BOT; } - @Override public TypeInteger lub() { return TOP; } - @Override public TypeInteger makeInit() { return ZERO; } - @Override public TypeInteger makeZero() { return ZERO; } - @Override public TypeInteger nonZero() { - if( isHigh() ) return this; - if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL - if( _max==0 ) return make(_min,-1); - return this; - } - @Override int hash() { return (int)((_min ^ (_min>>32)) *(_max ^ (_max>>32))); } - @Override - public boolean eq( Type t ) { - TypeInteger i = (TypeInteger)t; // Contract - return _min==i._min && _max==i._max; - } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java deleted file mode 100644 index 6dcd401a..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.lang.Long; -import java.util.BitSet; -import java.util.ArrayList; - - -/** - * Represents a slice of memory corresponding to a set of aliases - */ -public class TypeMem extends Type { - - // Which slice of memory? - // 0 means TOP, no slice. - // -1 means BOT, all memory. - // N means slice#N. - public final int _alias; - public final Type _t; // Memory contents, some scalar type - - private TypeMem(int alias, Type t) { super(TMEM); _alias = alias; _t = t; } - - public static TypeMem make(int alias, Type t) { return new TypeMem(alias,t).intern(); } - public static final TypeMem TOP = make(-1, Type.TOP ); - public static final TypeMem BOT = make(-1, Type.BOTTOM); - - public static void gather(ArrayList ts) { ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); } - - @Override - TypeMem xmeet(Type t) { - TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - int alias = _alias==that._alias ? _alias : -1; - Type mt = _t.meet(that._t); - return make(alias,mt); - } - - @Override - public Type dual() { - return make(_alias,_t.dual()); - } - - @Override public Type glb() { return make(_alias,_t.glb()); } - @Override public Type lub() { return make(_alias,_t.lub()); } - - @Override - int hash() { return 9876543 + _alias + _t.hashCode(); } - - @Override - boolean eq(Type t) { - TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t == that._t; - } - - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("MemBot"); - if( this==TOP ) return sb.append("MemTop"); - sb.append("MEM#"); - if( _alias== 0 ) return sb.append("TOP"); - if( _alias==-1 ) return sb.append("BOT"); - return _t.print(sb.append(_alias).append(":")); - } - - @Override public String str() { return toString(); } -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java deleted file mode 100644 index 9e33e15d..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.seaofnodes.simple.type; - -import java.util.ArrayList; - -/** - * Represents a Pointer to memory. - * - * Null is generic pointer to non-existent memory. - * *void is a non-Null pointer to all possible refs, both structs and arrays. - * Pointers can be to specific struct and array types, or a union with Null. - * The distinguished *$BOT ptr represents union of *void and Null. - * The distinguished *$TOP ptr represents the dual of *$BOT. - */ -public class TypeMemPtr extends Type { - // A TypeMemPtr is pair (obj,nil) - // where obj is a TypeStruct, possibly TypeStruct.BOT - // where nil is an explicit null is allowed or not - - // Examples: - // (Person,false) - a not-nil Person - // (Person,true ) - a Person or a nil - // (BOT ,false) - a not-nil void* (unspecified struct) - // (TOP ,true ) - a nil - - public final TypeStruct _obj; - public final boolean _nil; - - private TypeMemPtr(TypeStruct obj, boolean nil) { - super(TMEMPTR); - assert obj!=null; - _obj = obj; - _nil = nil; - } - public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } - public static TypeMemPtr make(TypeStruct obj) { return make(obj, false); } - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(obj, _nil); } - public TypeMemPtr makeFrom(boolean nil) { return nil==_nil ? this : make(_obj, nil); } - @Override public TypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); } - @Override public boolean isFinal() { return _obj.isFinal(); } - - // An abstract pointer, pointing to either a Struct or an Array. - // Can also be null or not. - public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); - public static final TypeMemPtr TOP = BOT.dual(); - // An abstract null (can be Struct or Array) or not-null C void* - public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); - public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null - - public static final TypeMemPtr TEST= make(TypeStruct.TEST,false); - public static void gather(ArrayList ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); } - - @Override - Type xmeet(Type t) { - TypeMemPtr that = (TypeMemPtr) t; - return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil); - } - - @Override - public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil); } - - @Override public TypeMemPtr glb() { return make(_obj.glb(),true ); } - @Override public TypeMemPtr lub() { return make(_obj.lub(),false); } - // Is forward-reference - @Override public boolean isFRef() { return _obj.isFRef(); } - @Override public Type makeInit() { return _nil ? NULLPTR : Type.TOP; } - @Override public Type makeZero() { return NULLPTR; } - @Override public Type nonZero() { return VOIDPTR; } - - @Override public boolean isHigh() { return this==TOP; } - @Override public boolean isConstant() { return this==NULLPTR; } - @Override public boolean isHighOrConst() { return this==TOP || this==NULLPTR; } - - @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers - - @Override - int hash() { return _obj.hashCode() ^ (_nil ? 1024 : 0); } - - @Override - boolean eq(Type t) { - TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _obj == ptr._obj && _nil == ptr._nil; - } - - // [void,name,MANY]*[,?] - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("PtrBot"); - if( this==TOP ) return sb.append("PtrTop"); - if( this== NULLPTR) return sb.append("null"); - if( this== VOIDPTR) return sb.append("*void"); - return _obj.print(sb.append("*")).append(_nil ? "?" : ""); - } - - @Override public String str() { - if( this==BOT ) return "PtrBot"; - if( this==TOP ) return "PtrTop"; - if( this== NULLPTR) return "null"; - if( this== VOIDPTR) return "*void"; - return "*"+_obj.str()+(_nil ? "?" : ""); - } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java deleted file mode 100644 index 97664e3c..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ /dev/null @@ -1,262 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.Utils; -import java.util.ArrayList; - -/** - * Represents a struct type. - */ -public class TypeStruct extends Type { - - // A Struct has a name and a set of fields; the fields themselves have - // names, types and aliases. The name has no semantic meaning, but is - // useful for debugging. Briefly during parsing a Struct can be a - // forward-ref; in this case the _fields array is null. - - // Its illegal to attempt to load a field from a forward-ref struct. - - // Example: "int rez = new S.x; struct S { int x; } return rez;" // Error, S not defined - // Rewrite: "struct S { int x; } int rez = new S.x; return rez;" // Ok, no forward ref - // - // During the normal optimization run, struct types "bottom out" at further - // struct references, so we don't have to handle e.g. cyclic types. The - // "bottom out" is again the forward-ref struct. - public final String _name; - public final Field[] _fields; - - TypeStruct(String name, Field[] fields) { - super(TSTRUCT); - _name = name; - _fields = fields; - } - - // All fields directly listed - public static TypeStruct make(String name, Field... fields) { return new TypeStruct(name, fields).intern(); } - public static final TypeStruct TOP = make("$TOP",new Field[0]); - public static final TypeStruct BOT = make("$BOT",new Field[0]); - public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); - // Forward-ref version - public static TypeStruct makeFRef(String name) { return make(name, (Field[])null); } - // Make a read-only version - @Override public TypeStruct makeRO() { - if( isFinal() ) return this; - Field[] flds = new Field[_fields.length]; - for( int i=0; i ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); } - - // Find field index by name - public int find(String fname) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._fname.equals(fname) ) - return i; - return -1; - } - public Field field(String fname) { - int idx = find(fname); - return idx== -1 ? null : _fields[idx]; - } - - // Find field index by alias - public int findAlias( int alias ) { - for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._alias==alias ) - return i; - return -1; - } - - - @Override - Type xmeet(Type t) { - TypeStruct that = (TypeStruct) t; - if( this==TOP ) return that; - if( that==TOP ) return this; - if( this==BOT ) return BOT; - if( that==BOT ) return BOT; - - // Within the same compilation unit, struct names are unique. If the - // names differ, its different structs. Across many compilation units, - // structs with the same name but different field layouts can be - // interned... which begs the question: - // "What is the meet of structs from two different compilation units?" - // And the answer is: "don't ask". - if( !_name.equals(that._name) ) - return BOT; // It's a struct; that's about all we know - if( this._fields==null ) return that; - if( that._fields==null ) return this; - if( _fields.length != that._fields.length ) return BOT; - // Just do field meets - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) { - Field f0 = _fields[i], f1 = that._fields[i]; - if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias ) - return BOT; - flds[i] = (Field)f0.meet(f1); - } - return make(_name,flds); - } - - @Override - public TypeStruct dual() { - if( this==TOP ) return BOT; - if( this==BOT ) return TOP; - if( _fields == null ) return this; - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].dual(); - return make(_name,flds); - } - - // Keeps the same struct, but lower-bounds all fields. - @Override public TypeStruct glb() { - if( _glb() ) return this; - // Need to glb each field - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].glb(); - return make(_name,flds); - } - private boolean _glb() { - if( _fields!=null ) - for( Field f : _fields ) - if( f.glb() != f ) - return false; - return true; - } - @Override public TypeStruct lub() { - if( _lub() ) return this; - // Need to glb each field - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].lub(); - return make(_name,flds); - } - private boolean _lub() { - if( _fields!=null ) - for( Field f : _fields ) - if( f.lub() != f ) - return false; - return true; - } - - - // Is forward-reference - @Override public boolean isFRef() { return _fields==null; } - // All fields are final - @Override public boolean isFinal() { - if( _fields==null ) return true; - for( Field fld : _fields ) - if( !fld.isFinal() ) - return false; - return true; - } - - @Override - boolean eq(Type t) { - TypeStruct ts = (TypeStruct)t; // Invariant - if( !_name.equals(ts._name) ) - return false; - if( _fields == ts._fields ) return true; - if( _fields==null || ts._fields==null ) return false; - if( _fields.length!=ts._fields.length ) - return false; - for( int i = 0; i < _fields.length; i++ ) - if( _fields[i] != ts._fields[i] ) - return false; - return true; - } - - @Override - int hash() { - long hash = _name.hashCode(); - if( _fields != null ) - for( Field f : _fields ) - hash = Utils.rot(hash,13) ^ f.hashCode(); - return Utils.fold(hash); - } - - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("StructBot"); - if( this==TOP ) return sb.append("StructTop"); - sb.append(_name); - if( _fields == null ) return sb; // Forward reference struct, just print the name - sb.append(" {"); - for( Field f : _fields ) - f._type.print(sb).append(f._final ? " " : " !").append(f._fname).append("; "); - return sb.append("}"); - } - - @Override public String str() { - if( this==BOT ) return "StructBot"; - if( this==TOP ) return "StructTop"; - return _name; - } - - - public boolean isAry() { return _fields.length==2 && _fields[1]._fname.equals("[]"); } - - public int aryBase() { - assert isAry(); - if( _offs==null ) _offs = offsets(); - return _offs[1]; - } - public int aryScale() { - assert isAry(); - return _fields[1]._type.log_size(); - } - - - // Field offsets as packed byte offsets - private int[] _offs; // Lazily computed and not part of the type semantics - public int offset(int idx) { - if( _offs==null ) _offs = offsets(); - return _offs[idx]; - } - private int[] offsets() { // Field byte offsets - // Compute a layout for a collection of fields - assert _fields != null; // No forward refs - // Compute a layout - int[] cnts = new int[4]; // Count of fields at log field size - for( Field f : _fields ) - cnts[f._type.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) - int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass - // Compute offsets to the start of each power-of-2 aligned fields. - int[] offs = new int[4]; - for( int i=3; i>=0; i-- ) { - offs[i] = off; - off += cnts[i]<