From e5f4f7b85fbb91a293a7d89d7b85adfbaeeb51f4 Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Fri, 21 Jun 2024 06:13:09 -0700 Subject: Format whitespace (#86) * Trim trailing whitespace * Ensure final new lines * Convert line endings from CRLF to LF * Replace tabs with spaces --- .github/workflows/maven.yml | 2 +- .gitignore | 1 - README.md | 26 +- chapter01/README.md | 18 +- chapter01/docs/01-grammar.md | 4 +- chapter01/pom.xml | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 4 +- .../main/java/com/seaofnodes/simple/node/Node.java | 4 +- .../com/seaofnodes/simple/node/ReturnNode.java | 2 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 8 +- chapter02/README.md | 6 +- chapter02/docs/02-grammar.md | 10 +- chapter02/docs/02-lattice.gv | 2 +- chapter02/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 8 +- .../main/java/com/seaofnodes/simple/Parser.java | 4 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 6 +- .../com/seaofnodes/simple/node/ConstantNode.java | 8 +- .../java/com/seaofnodes/simple/node/DivNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 18 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 12 +- .../java/com/seaofnodes/simple/Chapter02Test.java | 8 +- chapter03/README.md | 46 +- chapter03/docs/03-grammar.md | 14 +- chapter03/docs/03-graph2.gv | 2 +- chapter03/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 6 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 6 +- .../com/seaofnodes/simple/node/ConstantNode.java | 8 +- .../java/com/seaofnodes/simple/node/DivNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 24 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 6 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 12 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 10 +- chapter04/README.md | 14 +- chapter04/docs/04-grammar.md | 16 +- chapter04/docs/04-post-peephole.gv | 2 +- chapter04/docs/04-pre-peephole.gv | 2 +- chapter04/docs/build-graphs.sh | 1 - chapter04/docs/lattice.gv | 2 +- chapter04/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 14 +- .../main/java/com/seaofnodes/simple/Parser.java | 12 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 16 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 6 +- .../java/com/seaofnodes/simple/node/DivNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 8 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 26 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 6 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 18 +- chapter05/README.md | 22 +- chapter05/docs/05-grammar.md | 16 +- chapter05/docs/05-graph2.gv | 2 +- chapter05/docs/05-graph3.gv | 2 +- chapter05/docs/05-graph4.gv | 2 +- chapter05/docs/05-graph5.gv | 2 +- chapter05/docs/05-graph6.gv | 2 +- chapter05/docs/05-graph8.gv | 2 +- chapter05/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 18 +- .../main/java/com/seaofnodes/simple/Parser.java | 20 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 22 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 6 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 8 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 28 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 4 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 8 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 2 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 2 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 56 +- chapter06/README.md | 18 +- chapter06/docs/06-grammar.md | 16 +- chapter06/docs/06-graph1-post.gv | 2 +- chapter06/docs/06-graph1-pre.gv | 2 +- chapter06/docs/06-graph2-post.gv | 2 +- chapter06/docs/06-graph3-nonconst.gv | 2 +- chapter06/docs/06-graph3-true.gv | 2 +- chapter06/docs/build-graphs.sh | 1 - chapter06/docs/lattice.gv | 2 +- chapter06/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 18 +- .../main/java/com/seaofnodes/simple/Parser.java | 20 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 26 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 6 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/IfNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 34 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 6 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 8 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 2 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 2 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 56 +- chapter07/README.md | 30 +- chapter07/docs/07-grammar.md | 16 +- chapter07/pom.xml | 2 +- .../com/seaofnodes/simple/GraphVisualizer.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 20 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 26 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 8 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/IfNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoopNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 38 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 8 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 10 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 2 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 2 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 58 +- chapter08/README.md | 8 +- chapter08/docs/08-grammar.md | 20 +- chapter08/pom.xml | 2 +- .../java/com/seaofnodes/simple/GraphEvaluator.java | 362 +++--- .../com/seaofnodes/simple/GraphVisualizer.java | 12 +- .../main/java/com/seaofnodes/simple/Parser.java | 22 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 26 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 8 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/IfNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoopNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 42 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 6 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 8 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 2 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 2 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 72 +- .../java/com/seaofnodes/simple/Fuzzer08Test.java | 52 +- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 256 ++--- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 116 +- .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 288 ++--- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 924 +++++++-------- chapter09/README.md | 22 +- chapter09/docs/09-grammar.md | 20 +- chapter09/docs/09-graph1.gv | 2 +- chapter09/docs/09-graph2.gv | 2 +- chapter09/docs/09-graph3.gv | 2 +- chapter09/docs/09-graph6.gv | 2 +- chapter09/pom.xml | 2 +- .../java/com/seaofnodes/simple/GraphEvaluator.java | 362 +++--- .../com/seaofnodes/simple/GraphVisualizer.java | 12 +- .../main/java/com/seaofnodes/simple/IterPeeps.java | 2 +- .../main/java/com/seaofnodes/simple/Parser.java | 20 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/AddNode.java | 26 +- .../java/com/seaofnodes/simple/node/BoolNode.java | 4 +- .../com/seaofnodes/simple/node/ConstantNode.java | 8 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/IfNode.java | 4 +- .../java/com/seaofnodes/simple/node/LoopNode.java | 2 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 6 +- .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 50 +- .../java/com/seaofnodes/simple/node/NotNode.java | 6 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 4 +- .../java/com/seaofnodes/simple/node/ProjNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 4 +- .../com/seaofnodes/simple/node/ReturnNode.java | 4 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 8 +- .../java/com/seaofnodes/simple/node/StartNode.java | 2 +- .../java/com/seaofnodes/simple/node/StopNode.java | 4 +- .../java/com/seaofnodes/simple/node/SubNode.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 18 +- .../com/seaofnodes/simple/type/TypeInteger.java | 4 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 6 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter02Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 40 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 5 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 6 +- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 340 +++--- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 276 ++--- .../java/com/seaofnodes/simple/fuzzer/Reducer.java | 288 ++--- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 912 +++++++-------- chapter10/README.md | 36 +- chapter10/docs/10-grammar.md | 24 +- chapter10/docs/build-graphs.sh | 1 - chapter10/docs/example2b.gv | 2 +- chapter10/docs/example2c.gv | 2 +- chapter10/docs/lattice.gv | 2 +- chapter10/pom.xml | 2 +- .../src/main/java/com/seaofnodes/simple/Utils.java | 2 +- .../java/com/seaofnodes/simple/node/DivNode.java | 4 +- .../java/com/seaofnodes/simple/node/MulNode.java | 6 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 2 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter02Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 6 +- .../java/com/seaofnodes/simple/SchedulerTest.java | 148 +-- .../com/seaofnodes/simple/evaluator/Evaluator.java | 418 +++---- .../com/seaofnodes/simple/evaluator/Scheduler.java | 1184 ++++++++++---------- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 8 +- 273 files changed, 4018 insertions(+), 4025 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b810104b..26c2a083 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -16,4 +16,4 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Build with Maven - run: mvn --batch-mode --update-snapshots verify \ No newline at end of file + run: mvn --batch-mode --update-snapshots verify diff --git a/.gitignore b/.gitignore index af200c8f..4fe6bc54 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,3 @@ TAGS # Libs */lib/*jar - diff --git a/README.md b/README.md index 75115de3..df2ff06c 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,39 @@ # Simple A Simple showcase for the Sea-of-Nodes compiler IR -This repo is intended to demonstrate the Sea-of-Nodes compiler IR. +This repo is intended to demonstrate the Sea-of-Nodes compiler IR. -The Sea-of-Nodes is the core IR inside of HotSpot's C2 compiler +The Sea-of-Nodes is the core IR inside of HotSpot's C2 compiler and Google's V8 compiler and Sun/Oracle's Graal compiler. -Since we are show casing the SoN IR, the *language* being implemented is less important. +Since we are show casing the SoN IR, the *language* being implemented is less important. We're using a very simple language similar to C or Java, but with far fewer features. -The Sea-of-Nodes is used for machine code generation in these industrial -strength systems - but for this demonstration the backend is both difficult +The Sea-of-Nodes is used for machine code generation in these industrial +strength systems - but for this demonstration the backend is both difficult and less important. -This repo also is not intended to be a complete language in any sense, +This repo also is not intended to be a complete language in any sense, and so the backend will probably start with levering C or Java. ## Chapters The following is a rough plan, subject to change. -Each chapter will be self-sufficient and complete; in the sense that each chapter will fully implement -a subset of the simple language, and include everything that was created in the previous chapter. -Each chapter will also include a detailed commentary on relevant aspects of the +Each chapter will be self-sufficient and complete; in the sense that each chapter will fully implement +a subset of the simple language, and include everything that was created in the previous chapter. +Each chapter will also include a detailed commentary on relevant aspects of the Sea Of Nodes intermediate representation. -The simple language will be a small subset of C. +The simple language will be a small subset of C. * [Chapter 1](chapter01/README.md): Script that returns an integer literal, i.e., an empty function that takes no arguments and returns a single integer value. The `return` statement. * [Chapter 2](chapter02/README.md): Simple binary arithmetic such as addition, subtraction, multiplication, division with constants. Peephole optimization / simple constant folding. * [Chapter 3](chapter03/README.md): Local variables, and assignment statements. Read on RHS, SSA, more peephole optimization if local is a constant. -* [Chapter 4](chapter04/README.md): A non-constant external variable input named `arg`. Binary and Comparison operators involving constants and `arg`. Non-zero values will be truthy. Peephole optimizations involving algebraic simplifications. +* [Chapter 4](chapter04/README.md): A non-constant external variable input named `arg`. Binary and Comparison operators involving constants and `arg`. Non-zero values will be truthy. Peephole optimizations involving algebraic simplifications. * [Chapter 5](chapter05/README.md): `if` statement. CFG construction. -* [Chapter 6](chapter06/README.md): Peephole optimization around dead control flow. +* [Chapter 6](chapter06/README.md): Peephole optimization around dead control flow. * [Chapter 7](chapter07/README.md): `while` statement. Looping construct - eager phi approach. * [Chapter 8](chapter08/README.md): Looping construct continued, lazy phi creation, `break` and `continue` statements. * [Chapter 9](chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists. @@ -46,4 +46,4 @@ The simple language will be a small subset of C. * Chapter 16: Code generation: perhaps to Java bytecodes. * Chapter 17: Code generation: to native X86 or ARM. Instruction selection, BURS. Register allocation. * Chapter 18: Exceptions -* Chapter 19: Garbage Collection. \ No newline at end of file +* Chapter 19: Garbage Collection. diff --git a/chapter01/README.md b/chapter01/README.md index fef83683..d4882f8a 100644 --- a/chapter01/README.md +++ b/chapter01/README.md @@ -8,9 +8,9 @@ return 1; We implement the `return` statement. The `return` statement accepts an `expression` as an argument. -The only `expression` type is an integer literal, as shown in the example above. +The only `expression` type is an integer literal, as shown in the example above. -Here is the [complete language grammar](docs/01-grammar.md) for this chapter. +Here is the [complete language grammar](docs/01-grammar.md) for this chapter. To implement this simple language, we introduce a few key components and data structures. @@ -25,7 +25,7 @@ If necessary the reader can consult a standard compiler text book. ## Architecture -We construct the intermediate Sea of Nodes (SoN) representation directly as we parse the language. There is no Abstract Syntax Tree representation. The reason for this is to demonstrate a key benefit of the SoN IR: +We construct the intermediate Sea of Nodes (SoN) representation directly as we parse the language. There is no Abstract Syntax Tree representation. The reason for this is to demonstrate a key benefit of the SoN IR: a number of pessimistic peephole optimizations can be performed while parsing a language. This aspect is more fully explored from [Chapter 2](../chapter02/README.md) onwards. ## Data Structures @@ -43,7 +43,7 @@ representation follow. ### Intermediate Representation as a Graph of Nodes The intermediate representation is a graph of Node objects. The `Node` class is the base type for objects in the IR graph. -The `Node` class provides common capabilities that are inherited by all subtypes. +The `Node` class provides common capabilities that are inherited by all subtypes. Each subtype implements semantics relevant to that subtype. Each `Node` represents an "instruction" as it may appear in traditional IRs. @@ -109,8 +109,8 @@ The following control and data nodes appear in this chapter. | Return | Control | Represents the termination of a function | Predecessor control node, Data node value | Return value of the function | | Constant | Data | Represents constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant | -Within a traditional basic block, instructions are executed in sequence. In the Sea of Nodes model, the correct sequence of instructions is determined by a scheduling -algorithm that depends only on dependencies between nodes (including control dependencies) that are explicit as edges in the graph. This enables a number of optimizations +Within a traditional basic block, instructions are executed in sequence. In the Sea of Nodes model, the correct sequence of instructions is determined by a scheduling +algorithm that depends only on dependencies between nodes (including control dependencies) that are explicit as edges in the graph. This enables a number of optimizations at very little cost (nearly always small constant time) because all dependencies are always available. ### Unique Node ID @@ -122,8 +122,8 @@ graph. We discuss Node equality in [Chapter 9](../chapter09/README.md). ### Start Node -The Start node represents the start of the function. For now, we do not have any inputs to Start because our function does not -yet accept parameters. When we add parameters the value of Start will be a tuple, and will require Projections to extract the values. +The Start node represents the start of the function. For now, we do not have any inputs to Start because our function does not +yet accept parameters. When we add parameters the value of Start will be a tuple, and will require Projections to extract the values. We discuss this in detail in [Chapter 4](../chapter04/README.md). ### Constant Node @@ -145,7 +145,7 @@ The Return node has two inputs. The first input is a control node and the second is the data node that supplies the return value. In this presentation, Return functions as a Stop node, since multiple `return` statements are not possible. -The Stop node will be introduced in [Chapter 5](../chapter05/README.md) when we implement `if` statements. +The Stop node will be introduced in [Chapter 5](../chapter05/README.md) when we implement `if` statements. The Return's output is the value from the data node. diff --git a/chapter01/docs/01-grammar.md b/chapter01/docs/01-grammar.md index 5d9f3cf7..c5ddae2c 100644 --- a/chapter01/docs/01-grammar.md +++ b/chapter01/docs/01-grammar.md @@ -22,5 +22,5 @@ primaryExpression INTEGER_LITERAL : [1-9][0-9]* | [0] - ; -``` \ No newline at end of file + ; +``` diff --git a/chapter01/pom.xml b/chapter01/pom.xml index 2204bde8..08af80e1 100644 --- a/chapter01/pom.xml +++ b/chapter01/pom.xml @@ -11,4 +11,4 @@ chapter01 jar Chapter 1 - \ No newline at end of file + diff --git a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java index e2c1ca71..d79d23ff 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java @@ -194,7 +194,7 @@ public class Parser { return Long.parseLong(snum); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -210,7 +210,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java index cf465e94..5d74e206 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java @@ -52,7 +52,7 @@ public abstract class Node { if( n != null ) n._outputs.add( this ); } - + /** * Gets the ith input node * @param i Offset of the input node @@ -67,7 +67,7 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { return false; } - + /** * Used to allow repeating tests in the same JVM. This just resets the diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 5e083cf2..85bbc83f 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -19,5 +19,5 @@ public class ReturnNode extends Node { public Node expr() { return in(1); } @Override public boolean isCFG() { return true; } - + } diff --git a/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java index cb5a8a69..738ed702 100644 --- a/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -17,7 +17,7 @@ public class Chapter01Test { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); StartNode start = Parser.START; - + assertEquals(start, ret.ctrl()); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { @@ -40,7 +40,7 @@ public class Chapter01Test { @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -50,7 +50,7 @@ public class Chapter01Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -67,7 +67,7 @@ public class Chapter01Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter02/README.md b/chapter02/README.md index b5cb5dce..4b912465 100644 --- a/chapter02/README.md +++ b/chapter02/README.md @@ -7,7 +7,7 @@ multiplication, division, and unary minus. This allows us to write statements su return 1 + 2 * 3 + -5; ``` -Here is the [complete language grammar](docs/02-grammar.md) for this chapter. +Here is the [complete language grammar](docs/02-grammar.md) for this chapter. ## Extensions to Intermediate Representation @@ -96,7 +96,7 @@ structure: Our lattice elements can be one of three types: * The highest element is "top", denoted by T; assigning T means that the Node's value may or may not be a compile time constant. -* All elements in the middle are constants. +* All elements in the middle are constants. * The lowest is "bottom", denoted by ⊥; assigning ⊥ means that we know that the Node's value is **not** a compile time constant. An invariant of peephole optimizations is that the type of a Node always moves down the lattice. @@ -121,4 +121,4 @@ The following visual shows how the graph looks like pre-peephole optimization: ## Post-peephole -![Example Visual](./docs/02-post-peephole-ex1.svg) \ No newline at end of file +![Example Visual](./docs/02-post-peephole-ex1.svg) diff --git a/chapter02/docs/02-grammar.md b/chapter02/docs/02-grammar.md index 1a1e1239..ce875eda 100644 --- a/chapter02/docs/02-grammar.md +++ b/chapter02/docs/02-grammar.md @@ -14,11 +14,11 @@ statement expression : additiveExpression ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -36,5 +36,5 @@ primaryExpression INTEGER_LITERAL : [1-9][0-9]* | [0] - ; -``` \ No newline at end of file + ; +``` diff --git a/chapter02/docs/02-lattice.gv b/chapter02/docs/02-lattice.gv index 57f14d77..4faa0e11 100644 --- a/chapter02/docs/02-lattice.gv +++ b/chapter02/docs/02-lattice.gv @@ -3,7 +3,7 @@ digraph Lattice { node [shape=circle, style=filled, fillcolor=lightblue]; Top [label="⊤"]; Bottom [label="⊥"]; - + A [label="MIN_INT"]; B [label="..." shape=none style=none]; C [label="-1"]; diff --git a/chapter02/pom.xml b/chapter02/pom.xml index 5ced95c3..acc963b0 100644 --- a/chapter02/pom.xml +++ b/chapter02/pom.xml @@ -11,4 +11,4 @@ chapter02 jar Chapter 2 - \ No newline at end of file + diff --git a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index df128674..4c625be1 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -21,7 +21,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -35,13 +35,13 @@ public class GraphVisualizer { // Merge multiple edges hitting the same node. Makes common shared // nodes much prettier to look at. sb.append("\tconcentrate=\"true\";\n"); - + // Just the Nodes first, in a cluster no edges nodes(sb, all); // Walk the Node edges nodeEdges(sb, all); - + sb.append("}\n"); return sb.toString(); } @@ -89,7 +89,7 @@ public class GraphVisualizer { } } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java index f5aca5ba..155cd353 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java @@ -244,7 +244,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -260,7 +260,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/Utils.java b/chapter02/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java index a0376dd4..2d58cb75 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -6,7 +6,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -15,7 +15,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+")); return sb.append(")"); } - + @Override public Type compute() { @@ -32,5 +32,5 @@ public class AddNode extends Node { // TODO: add of 0 return null; } - + } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index ec85bf1f..e4d7f412 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -21,10 +21,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return "#"+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -32,11 +32,11 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb) { return _con._print(sb); } - + @Override public Type compute() { return _con; } @Override public Node idealize() { return null; } - + } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java index 4f502e5f..e3c3616a 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -16,7 +16,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 08924563..0d59ef25 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -7,15 +7,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java index c6718d27..26c28281 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -7,7 +7,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java index 960742e8..7e5f2701 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java @@ -74,9 +74,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -103,7 +103,7 @@ public abstract class Node { // Every Node implements this. abstract StringBuilder _print1(StringBuilder sb); - + /** * Gets the ith input node * @param i Offset of the input node @@ -120,13 +120,13 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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}. - * + * * @param idx which def to set * @param new_def the new definition * @return new_def for flow coding @@ -195,7 +195,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -206,15 +206,15 @@ public abstract class Node { } // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) return n; - + return this; // No progress } - + /** * This function needs to be * Monotonic diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index a0f2e931..7fa470f8 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -19,7 +19,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -29,7 +29,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java index 6eda4f11..d2e32c61 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -16,7 +16,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter02/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter02/src/main/java/com/seaofnodes/simple/type/Type.java index 5bd51611..89f6c715 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 69fb811c..13f816bf 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -6,16 +6,16 @@ package com.seaofnodes.simple.type; public class TypeInteger extends Type { public final static TypeInteger ZERO= new TypeInteger(0); - + /** * The constant value observed for this type */ private final long _con; - public TypeInteger(long con) { - super(TINT); - _con = con; - } + public TypeInteger(long con) { + super(TINT); + _con = con; + } public static TypeInteger constant(long con) { return new TypeInteger(con); } @@ -24,7 +24,7 @@ public class TypeInteger extends Type { return _print(new StringBuilder()).toString(); } - @Override + @Override public StringBuilder _print(StringBuilder sb) { return sb.append(_con); } @Override diff --git a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 971b60ba..35464955 100644 --- a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -73,7 +73,7 @@ public class Chapter02Test { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); StartNode start = Parser.START; - + assertEquals(start, ret.ctrl()); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { @@ -96,7 +96,7 @@ public class Chapter02Test { @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -106,7 +106,7 @@ public class Chapter02Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -122,7 +122,7 @@ public class Chapter02Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter03/README.md b/chapter03/README.md index 9b4898ff..7b0d22cb 100644 --- a/chapter03/README.md +++ b/chapter03/README.md @@ -3,13 +3,13 @@ In this chapter we extend the language grammar to allow variable declarations. This allows us to write: ``` -int a=1; -int b=2; -int c=0; -{ - int b=3; - c=a+b; -} +int a=1; +int b=2; +int c=0; +{ + int b=3; + c=a+b; +} return c; ``` @@ -45,7 +45,7 @@ We maintain a stack of symbol tables. When a new scope is created, we push a new symbol table to the stack, when scope exits, the topmost symbol table is popped from the stack. -Declaring a name adds it to the current symbol table. +Declaring a name adds it to the current symbol table. If a name is assigned to, then its mapping in the most recent symbol table is updated. If a name is accessed, its mapping is looked up in the symbol tables. The lookup goes up the stack of symbol tables. @@ -53,7 +53,7 @@ If a name is accessed, its mapping is looked up in the symbol tables. The lookup We wrap the stack of symbol tables in a `ScopeNode`. When a name binding is added to a symbol table, we also make the newly added node an input in the `ScopeNode`. This means that the `ScopeNode` is a user of -all nodes that define new names. +all nodes that define new names. From an implementation point of view, the symbol table maps names to offsets in the ScopeNode's inputs. @@ -67,13 +67,13 @@ the box. We show the edge from each variable in the symbol table to the defining We show the output from following code. ``` -01 int a=1; -02 int b=2; -03 int c=0; -04 { -05 int b=3; -07 c=a+b; -08 } +01 int a=1; +02 int b=2; +03 int c=0; +04 { +05 int b=3; +07 c=a+b; +08 } 09 return c; ``` @@ -83,12 +83,12 @@ The variables `a`, `b`, and `c` are registered in this table. * On line 5, a variable `b` is declared. `b` is registered in the symbol table at level 1. This then hides the variable with the same name on line 2. * On line 7, we update the variable `c`. Variable lookup will find it in the symbol table at level 0. -The node graph is shown below (note that peephole opts are switched off here so that we can +The node graph is shown below (note that peephole opts are switched off here so that we can see the graph as its initially constructed). ![Graph1](./docs/03-graph1.svg) -The diagram shows the ScopeNode and the symbol tables in the bottom cluster. +The diagram shows the ScopeNode and the symbol tables in the bottom cluster. Each table is annotated by its scope level, and shows the variables defined in that scope. * When we exit the nested scope on line 8, the symbol table at level 1 is popped. @@ -104,10 +104,10 @@ Once we enable peephole optimizations, the parser is able to reduce all the expr constant. This is illustrated nicely below. ``` -01 int x0=1; -02 int y0=2; -03 int x1=3; -04 int y1=4; +01 int x0=1; +02 int y0=2; +03 int x1=3; +04 int y1=4; 05 return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); ``` @@ -115,4 +115,4 @@ The graph for above, is displayed below. ![Graph2](./docs/03-graph3.svg) -The return node has the constant `8` as its data value! \ No newline at end of file +The return node has the constant `8` as its data value! diff --git a/chapter03/docs/03-grammar.md b/chapter03/docs/03-grammar.md index 2a6d4c1b..14a7d5fa 100644 --- a/chapter03/docs/03-grammar.md +++ b/chapter03/docs/03-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | metaStatement ; @@ -38,11 +38,11 @@ returnStatement expression : additiveExpression ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -62,11 +62,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter03/docs/03-graph2.gv b/chapter03/docs/03-graph2.gv index f570b13e..460b63c8 100644 --- a/chapter03/docs/03-graph2.gv +++ b/chapter03/docs/03-graph2.gv @@ -32,4 +32,4 @@ int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph; Scope1_0:"Scope1_0_a" -> Con_3; Scope1_0:"Scope1_0_b" -> Con_4; Scope1_0:"Scope1_0_c" -> Add7; -} \ No newline at end of file +} diff --git a/chapter03/pom.xml b/chapter03/pom.xml index a484d5bd..2b977285 100644 --- a/chapter03/pom.xml +++ b/chapter03/pom.xml @@ -11,4 +11,4 @@ chapter03 jar Chapter 3 - \ No newline at end of file + diff --git a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index a85a670e..9dfac52d 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -21,7 +21,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -35,10 +35,10 @@ public class GraphVisualizer { // Merge multiple edges hitting the same node. Makes common shared // nodes much prettier to look at. sb.append("\tconcentrate=\"true\";\n"); - + // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges scopes(sb, parser._scope); @@ -47,7 +47,7 @@ public class GraphVisualizer { // Walk the Scope edges scopeEdges(sb, parser._scope); - + sb.append("}\n"); return sb.toString(); } @@ -124,7 +124,7 @@ public class GraphVisualizer { } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -144,7 +144,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java index 60761f04..9c7ebaca 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java @@ -352,7 +352,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -379,7 +379,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -395,7 +395,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/Utils.java b/chapter03/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java index a0376dd4..2d58cb75 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -6,7 +6,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -15,7 +15,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+")); return sb.append(")"); } - + @Override public Type compute() { @@ -32,5 +32,5 @@ public class AddNode extends Node { // TODO: add of 0 return null; } - + } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index ec85bf1f..e4d7f412 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -21,10 +21,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return "#"+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -32,11 +32,11 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb) { return _con._print(sb); } - + @Override public Type compute() { return _con; } @Override public Node idealize() { return null; } - + } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java index 4f502e5f..e3c3616a 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -16,7 +16,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 08924563..0d59ef25 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -7,15 +7,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java index c6718d27..26c28281 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -7,7 +7,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java index 91bde29d..504b8e24 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java @@ -74,9 +74,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -103,7 +103,7 @@ public abstract class Node { // Every Node implements this. abstract StringBuilder _print1(StringBuilder sb); - + /** * Gets the ith input node * @param i Offset of the input node @@ -120,21 +120,21 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -193,7 +193,7 @@ public abstract class Node { 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 @@ -228,7 +228,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -239,14 +239,14 @@ public abstract class Node { } // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) return n; - + return this; // No progress } - + /** * This function needs to be * Monotonic diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index a0f2e931..7fa470f8 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -19,7 +19,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -29,7 +29,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 7b42a2d0..e9ff0f44 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -21,8 +21,8 @@ public class ScopeNode extends Node { _scopes = new Stack<>(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -58,7 +58,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java index 6eda4f11..d2e32c61 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -16,7 +16,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java index 5bd51611..89f6c715 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 69fb811c..13f816bf 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -6,16 +6,16 @@ package com.seaofnodes.simple.type; public class TypeInteger extends Type { public final static TypeInteger ZERO= new TypeInteger(0); - + /** * The constant value observed for this type */ private final long _con; - public TypeInteger(long con) { - super(TINT); - _con = con; - } + public TypeInteger(long con) { + super(TINT); + _con = con; + } public static TypeInteger constant(long con) { return new TypeInteger(con); } @@ -24,7 +24,7 @@ public class TypeInteger extends Type { return _print(new StringBuilder()).toString(); } - @Override + @Override public StringBuilder _print(StringBuilder sb) { return sb.append(_con); } @Override diff --git a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 5847fed4..965a100a 100644 --- a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -52,7 +52,7 @@ public class Chapter03Test { @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -120,7 +120,7 @@ public class Chapter03Test { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); StartNode start = Parser.START; - + assertEquals(start, ret.ctrl()); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { @@ -143,7 +143,7 @@ public class Chapter03Test { @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -153,7 +153,7 @@ public class Chapter03Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -169,7 +169,7 @@ public class Chapter03Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter04/README.md b/chapter04/README.md index 4ae4d432..03c6c53f 100644 --- a/chapter04/README.md +++ b/chapter04/README.md @@ -19,7 +19,7 @@ Following are revised or new nodes |-----------|----------------|------------------------------------------------|-----------------------|----------------------------------------------------------------------------| | MultiNode | Abstract class | A node that has a tuple result | | A tuple | | Start | Control | Start of function, now a MultiNode | | A tuple with a ctrl token and an `arg` data node | -| Proj | Data | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | +| Proj | Data | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | | Bool | Data | Represents results of a comparison operator | Two data nodes | Result is a comparison, represented as integer value where 1=true, 0=false | | Not | Data | Logical not | One data node | Result converts 0 to 1 and vice versa | @@ -51,7 +51,7 @@ Start node. ## Changes to Type System -In [Chapter 2](../chapter02/README.md) we introduced the Type System. +In [Chapter 2](../chapter02/README.md) we introduced the Type System. We annotate Nodes with Types. @@ -128,7 +128,7 @@ clutter and help draw reader's attention to the more important aspects of the gr ## More Peephole Optimizations -Now that we have non-constant integer values, we do additional optimizations, rearranging algebraic +Now that we have non-constant integer values, we do additional optimizations, rearranging algebraic expressions to enable constant folding. For example: ``` @@ -162,9 +162,9 @@ Here is a (partial) list of peepholes introduced in this Chapter: | ((arg1 + con) + arg2) | ((arg1 + arg2) + con) | Move constants to right, to encourage folding | | (arg + arg) | (arg * 2) | Sum-of-products form | -## Code Walkthrough +## Code Walkthrough -The peephole optimizations introduced in this chapter are local. They are triggered during parsing +The peephole optimizations introduced in this chapter are local. They are triggered during parsing as new nodes are created, before the newly created node has any uses. For example, when we parse a unary expression, and create a Node for the parsed expression, @@ -199,7 +199,7 @@ by all subclasses of `Node`. public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + // Replace constant computations from non-constants with a constant node if (!(this instanceof ConstantNode) && type.isConstant()) return deadCodeElim(new ConstantNode(type).peephole()); @@ -209,7 +209,7 @@ public final Node peephole( ) { if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } ``` diff --git a/chapter04/docs/04-grammar.md b/chapter04/docs/04-grammar.md index 51d5dbea..48facd4c 100644 --- a/chapter04/docs/04-grammar.md +++ b/chapter04/docs/04-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | metaStatement ; @@ -38,15 +38,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -66,11 +66,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter04/docs/04-post-peephole.gv b/chapter04/docs/04-post-peephole.gv index 51e176bc..0a9f8e93 100644 --- a/chapter04/docs/04-post-peephole.gv +++ b/chapter04/docs/04-post-peephole.gv @@ -40,4 +40,4 @@ return 1+arg+2; #showGraph; Return12 -> Add11[taillabel=1]; edge [style=dashed color=cornflowerblue]; Scope1_0:"Scope1_0_arg" -> Start2:p1; -} \ No newline at end of file +} diff --git a/chapter04/docs/04-pre-peephole.gv b/chapter04/docs/04-pre-peephole.gv index b6fefc2d..aefd81c4 100644 --- a/chapter04/docs/04-pre-peephole.gv +++ b/chapter04/docs/04-pre-peephole.gv @@ -44,4 +44,4 @@ return 1+arg+2; #showGraph; Return9 -> Add8[taillabel=1]; edge [style=dashed color=cornflowerblue]; Scope1_0:"Scope1_0_arg" -> Start2:p1; -} \ No newline at end of file +} diff --git a/chapter04/docs/build-graphs.sh b/chapter04/docs/build-graphs.sh index d8038602..e736a386 100644 --- a/chapter04/docs/build-graphs.sh +++ b/chapter04/docs/build-graphs.sh @@ -1,2 +1 @@ dot -Tsvg lattice.gv > lattice.svg - diff --git a/chapter04/docs/lattice.gv b/chapter04/docs/lattice.gv index 2644448e..1f286530 100644 --- a/chapter04/docs/lattice.gv +++ b/chapter04/docs/lattice.gv @@ -60,4 +60,4 @@ digraph Lattice { INT_MAX; TUPLE} {rank=same; INT_BOT} {rank=same; BOT;} -} \ No newline at end of file +} diff --git a/chapter04/pom.xml b/chapter04/pom.xml index a69df6d0..0fdebb14 100644 --- a/chapter04/pom.xml +++ b/chapter04/pom.xml @@ -11,4 +11,4 @@ chapter04 jar Chapter 4 - \ No newline at end of file + diff --git a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index f624cbde..62b434f3 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -21,7 +21,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -35,10 +35,10 @@ public class GraphVisualizer { // Merge multiple edges hitting the same node. Makes common shared // nodes much prettier to look at. sb.append("\tconcentrate=\"true\";\n"); - + // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges scopes(sb, parser._scope); @@ -47,7 +47,7 @@ public class GraphVisualizer { // Walk the Scope edges scopeEdges(sb, parser._scope); - + sb.append("}\n"); return sb.toString(); } @@ -88,7 +88,7 @@ public class GraphVisualizer { } sb.append("\n"); sb.append("\t\t\t>\n\t\t"); - + } else { // control nodes have box shape // other nodes are ellipses, i.e. default shape @@ -156,7 +156,7 @@ public class GraphVisualizer { } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -179,7 +179,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java index b517d20e..e42613e2 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java @@ -14,7 +14,7 @@ import java.util.*; */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -56,12 +56,12 @@ public class Parser { @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); } @@ -395,7 +395,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -422,7 +422,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -438,7 +438,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/Utils.java b/chapter04/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java index d9fc6ad9..5720aca4 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -6,7 +6,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -15,7 +15,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+")); return sb.append(")"); } - + @Override public Type compute() { @@ -42,13 +42,13 @@ public class AddNode extends Node { // 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(); @@ -66,19 +66,19 @@ public class AddNode extends Node { return spline_cmp(lhs,rhs) ? swap12() : null; // Now we only see (add add non) - + // Do we have (x + con1) + con2? // Replace with (x + (con1+con2) which then fold the constants if( lhs.in(2)._type.isConstant() && t2.isConstant() ) return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); // Now we sort along the spline via rotates, to gather similar things together. - + // Do we rotate (x + y) + z // into (x + z) + y ? if( spline_cmp(lhs.in(2),rhs) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - + return null; } @@ -94,5 +94,5 @@ public class AddNode extends Node { // Same category of "others" return lo._nid > hi._nid; } - + } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java index be3ee46c..05495790 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -10,7 +10,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -39,7 +39,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index ba6f55d7..1086037d 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -21,10 +21,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return ""+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -32,7 +32,7 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb) { return _con._print(sb); } - + @Override public Type compute() { return _con; } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java index 86b1e95c..1404c900 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -16,7 +16,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 08924563..0d59ef25 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -7,15 +7,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java index 19f2a340..36d5eb3e 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -7,7 +7,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -34,16 +34,16 @@ public class MulNode extends Node { 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(); - + return null; } } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 81c90f68..7ba1aa19 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java index bebfd3a1..9fa0a95a 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java @@ -74,9 +74,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -100,7 +100,7 @@ public abstract class Node { // Every Node implements this. abstract StringBuilder _print1(StringBuilder sb); - + /** * Gets the ith input node * @param i Offset of the input node @@ -117,19 +117,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -188,7 +188,7 @@ public abstract class Node { 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 @@ -211,7 +211,7 @@ public abstract class Node { public N unkeep() { delUse(null); return (N)this; } // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -236,7 +236,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -245,13 +245,13 @@ public abstract class Node { return deadCodeElim(new ConstantNode(type).peephole()); // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } @@ -271,7 +271,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -340,7 +340,7 @@ public abstract class Node { // ------------------------------------------------------------------------ // Peephole utilities - + // Swap inputs without letting either input go dead during the swap. Node swap12() { Node tmp = in(1); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java index 5dd6a86e..e3683646 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -7,15 +7,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 5dd7a2f0..2f49fe91 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -10,7 +10,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index d0d3eb06..c9d6c9c8 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -19,7 +19,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -29,7 +29,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return new TypeTuple(ctrl()._type,expr()._type); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index c930fc65..8ffeac64 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -28,8 +28,8 @@ public class ScopeNode extends Node { _scopes = new Stack<>(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -65,7 +65,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java index e94c24e0..022e276d 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -13,7 +13,7 @@ import com.seaofnodes.simple.type.TypeTuple; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = new TypeTuple(args); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java index ec2168e6..27e77f90 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -16,7 +16,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter04/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter04/src/main/java/com/seaofnodes/simple/type/Type.java index 170325f0..1687f65a 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 3037e45f..9fb4b853 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public boolean isTop() { return !_is_con && _con==0; } public boolean isBot() { return !_is_con && _con==1; } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if (isTop()) return sb.append("IntTop"); if (isBot()) return sb.append("IntBot"); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 513972aa..25663dda 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; public TypeTuple(Type... _types) { diff --git a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 299399d2..5757fc59 100644 --- a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -40,7 +40,7 @@ public class Chapter04Test { ReturnNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testChapter4Peephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -54,7 +54,7 @@ public class Chapter04Test { ReturnNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testChapter4VarArg() { Parser parser = new Parser("return arg; #showGraph;", TypeInteger.BOT); @@ -114,7 +114,7 @@ public class Chapter04Test { @Test public void testChapter4Bug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -128,7 +128,7 @@ public class Chapter04Test { ReturnNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -168,7 +168,7 @@ public class Chapter04Test { @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -236,7 +236,7 @@ public class Chapter04Test { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); StartNode start = Parser.START; - + assertTrue(ret.ctrl() instanceof ProjNode); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { @@ -259,7 +259,7 @@ public class Chapter04Test { @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -269,7 +269,7 @@ public class Chapter04Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -285,7 +285,7 @@ public class Chapter04Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter05/README.md b/chapter05/README.md index f06e5e15..cf95b545 100644 --- a/chapter05/README.md +++ b/chapter05/README.md @@ -16,7 +16,7 @@ The following new nodes are introduced in this chapter: |-----------|---------|---------|----------------------------------------------------|------------------------------------------------|----------------------------------------------------| | If | Control | 5 | A branching test, sub type of `MultiNode` | A control node and a data predicate node | A tuple of two values: one for true, one for false | | Region | Control | 5 | A merge point for multiple control flows | An input for each control flow that is merging | Merged control | -| Phi | Data | 5 | A phi function picks a value based on control flow | A Region, and data nodes for each control path | Depends on control flow path taken | +| Phi | Data | 5 | A phi function picks a value based on control flow | A Region, and data nodes for each control path | Depends on control flow path taken | | Stop | Control | 5 | Termination of the program | All return nodes of the function | None | ## Recap @@ -27,7 +27,7 @@ Here is a recap of the nodes introduced in previous chapters: |-----------|----------------|---------|------------------------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Multi | Abstract class | 4 | A node that has a tuple result | | A tuple | | Start | Control | 1 | Start of function, now a MultiNode | | A tuple with a ctrl token and an `arg` data node | -| Proj | Data | 4 | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | +| Proj | Data | 4 | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | | Bool | Data | 4 | Represents results of a comparison operator | Two data nodes | Result is a comparison, represented as integer value where 1=true, 0=false | | Not | Data | 4 | Logical not | One data node | Result converts 0 to 1 and vice versa | | Return | Control | 1 | End of function | Predecessor control node and a data node for the return value of the function | Return value of the function | @@ -77,7 +77,7 @@ However: > 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 algorithm.[^3] -Thus, we do not associate a control edge on every data node in the graph. +Thus, we do not associate a control edge on every data node in the graph. We insert a `Region` node at a merge point where it takes control from each predecessor's control edge, and produces a merged control as output. Data flows @@ -105,7 +105,7 @@ This involves following: `else` statement we parse it. 7. At this point we have two `ScopeNode`s; the original one, potentially updated by the `True` branch, and the duplicate one, potentially updated by the - `False` branch. + `False` branch. 8. We create a `Region` node to represent a merge point. 9. We *merge* the two `ScopeNode`s. We create `Phi` nodes for any names whose bindings differ. The `Phi` nodes take the `Region` node as the control input @@ -117,7 +117,7 @@ Implementation is in [`parseIf` method in `Parser`](https://github.com/SeaOfNode ## Operations on ScopeNodes -As explained above, we duplicate ScopeNodes and merge them at a later point. There are some +As explained above, we duplicate ScopeNodes and merge them at a later point. There are some subtleties in how this is implemented that is worth going over. ### Duplicating a ScopeNode @@ -139,7 +139,7 @@ At the merge point we merge two ScopeNodes. The goals are: 2) A new Region node is created representing the merged control flow. The phis have this region node as the first input. 3) After the merge is completed, the duplicate is discarded, and its use of each of the nodes is also deleted. -The merging logic takes advantage of that fact that the two ScopeNodes have the bound nodes in the same order in the list of inputs. This was ensured during duplicating the ScopeNode. +The merging logic takes advantage of that fact that the two ScopeNodes have the bound nodes in the same order in the list of inputs. This was ensured during duplicating the ScopeNode. Although only the innermost occurrence of a name can have its binding changed, we scan all the nodes in our input list, and simply ignore ones where the binding has not changed. For implementation [see `ScopeNode.mergeScopes()`](https://github.com/SeaOfNodes/Simple/blob/main/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java#L164-L173) @@ -149,12 +149,12 @@ For implementation [see `ScopeNode.mergeScopes()`](https://github.com/SeaOfNodes We show the graph for the following code snippet: ```java -int a = 1; -if (arg == 1) - a = arg+2; +int a = 1; +if (arg == 1) + a = arg+2; else - a = arg-3; -return a; + a = arg-3; +return a; ``` ### Before Merging diff --git a/chapter05/docs/05-grammar.md b/chapter05/docs/05-grammar.md index 9af39c05..65be4bf7 100644 --- a/chapter05/docs/05-grammar.md +++ b/chapter05/docs/05-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | ifStatement | metaStatement @@ -43,15 +43,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -73,11 +73,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter05/docs/05-graph2.gv b/chapter05/docs/05-graph2.gv index 2adb3da5..12506e85 100644 --- a/chapter05/docs/05-graph2.gv +++ b/chapter05/docs/05-graph2.gv @@ -75,4 +75,4 @@ return a; Scope1_0:"Scope1_0_$ctrl" -> Region17; Scope1_0:"Scope1_0_arg" -> Start2:p1; Scope1_1:"Scope1_1_a" -> Phi_a18; -} \ No newline at end of file +} diff --git a/chapter05/docs/05-graph3.gv b/chapter05/docs/05-graph3.gv index 7e049c9c..277bed48 100644 --- a/chapter05/docs/05-graph3.gv +++ b/chapter05/docs/05-graph3.gv @@ -63,4 +63,4 @@ return a; Phi_a18 -> Sub16[taillabel=2]; Return19 -> Region17[taillabel=0 color=red]; Return19 -> Phi_a18[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter05/docs/05-graph4.gv b/chapter05/docs/05-graph4.gv index ac3dcdd7..88120b4a 100644 --- a/chapter05/docs/05-graph4.gv +++ b/chapter05/docs/05-graph4.gv @@ -60,4 +60,4 @@ return a; Phi_a17 -> EQNode7[taillabel=2]; Return18 -> Region16[taillabel=0 color=red]; Return18 -> Phi_a17[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter05/docs/05-graph5.gv b/chapter05/docs/05-graph5.gv index b6bfd5f2..b03e90b5 100644 --- a/chapter05/docs/05-graph5.gv +++ b/chapter05/docs/05-graph5.gv @@ -57,4 +57,4 @@ return a; EQNode20 -> Phi_a19[taillabel=2]; Return21 -> Region16[taillabel=0 color=red]; Return21 -> EQNode20[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter05/docs/05-graph6.gv b/chapter05/docs/05-graph6.gv index f888e8e9..1ad7b94a 100644 --- a/chapter05/docs/05-graph6.gv +++ b/chapter05/docs/05-graph6.gv @@ -55,4 +55,4 @@ return c; Phi_c17 -> Con_6[taillabel=2]; Return19 -> Region16[taillabel=0 color=red]; Return19 -> Phi_c17[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter05/docs/05-graph8.gv b/chapter05/docs/05-graph8.gv index bef29b93..2af43847 100644 --- a/chapter05/docs/05-graph8.gv +++ b/chapter05/docs/05-graph8.gv @@ -126,4 +126,4 @@ return a; edge [style=dashed color=cornflowerblue]; Scope1_0:"Scope1_0_arg" -> Start2:p1; Scope1_1:"Scope1_1_a" -> Phi_a34; -} \ No newline at end of file +} diff --git a/chapter05/pom.xml b/chapter05/pom.xml index 8fe63444..49991f74 100644 --- a/chapter05/pom.xml +++ b/chapter05/pom.xml @@ -11,4 +11,4 @@ chapter05 jar Chapter 5 - \ No newline at end of file + diff --git a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 1c3e314c..c35a4e32 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -21,7 +21,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -35,10 +35,10 @@ public class GraphVisualizer { // Merge multiple edges hitting the same node. Makes common shared // nodes much prettier to look at. sb.append("\tconcentrate=\"true\";\n"); - + // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges for (ScopeNode sn: parser._xScopes) scopes(sb, sn); @@ -49,7 +49,7 @@ public class GraphVisualizer { // Walk the Scope edges for (ScopeNode sn: parser._xScopes) scopeEdges(sb, sn); - + sb.append("}\n"); return sb.toString(); } @@ -90,7 +90,7 @@ public class GraphVisualizer { } sb.append("\n"); sb.append("\t\t\t>\n\t\t"); - + } else { // control nodes have box shape // other nodes are ellipses, i.e. default shape @@ -102,7 +102,7 @@ public class GraphVisualizer { } sb.append("];\n"); } - + // Force Region & Phis to line up for( Node n : all ) { if( n instanceof RegionNode region ) { @@ -114,7 +114,7 @@ public class GraphVisualizer { sb.append("}\n"); } } - + sb.append("\t}\n"); // End Node cluster } @@ -179,7 +179,7 @@ public class GraphVisualizer { } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -202,7 +202,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java index 99d38686..ff58fc25 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java @@ -13,7 +13,7 @@ import java.util.*; * This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -54,7 +54,7 @@ public class Parser { add("true"); }}; - + /** * 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 @@ -75,12 +75,12 @@ public class Parser { @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); } @@ -101,7 +101,7 @@ public class Parser { return STOP; } - + /** * Parses a block * @@ -165,7 +165,7 @@ public class Parser { ctrl(ifT); // set ctrl token to ifTrue projection parseStatement(); // Parse true-side ScopeNode tScope = _scope; - + // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF); // Ctrl token is now set to ifFalse projection @@ -176,7 +176,7 @@ public class Parser { 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 @@ -465,7 +465,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -492,7 +492,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -508,7 +508,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Utils.java b/chapter05/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java index 87304f28..081c9276 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -6,7 +6,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -15,7 +15,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+")); return sb.append(")"); } - + @Override public Type compute() { @@ -42,13 +42,13 @@ public class AddNode extends Node { // 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(); @@ -66,7 +66,7 @@ public class AddNode extends Node { return spline_cmp(lhs,rhs) ? swap12() : null; // Now we only see (add add non) - + // Do we have (x + con1) + con2? // Replace with (x + (con1+con2) which then fold the constants if( lhs.in(2)._type.isConstant() && t2.isConstant() ) @@ -76,7 +76,7 @@ public class AddNode extends Node { // 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...) - + // 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. @@ -91,12 +91,12 @@ public class AddNode extends Node { } // Now we sort along the spline via rotates, to gather similar things together. - + // Do we rotate (x + y) + z // into (x + z) + y ? if( spline_cmp(lhs.in(2),rhs) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - + return null; } @@ -108,16 +108,16 @@ public class AddNode extends Node { static boolean spline_cmp( Node hi, Node lo ) { if( lo._type.isConstant() ) return false; if( hi._type.isConstant() ) return true ; - + if( lo instanceof PhiNode && lo.allCons() ) return false; if( hi instanceof PhiNode && hi.allCons() ) return true ; if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - + // Same category of "others" return lo._nid > hi._nid; } - + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 61259d2c..7a4a087e 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -10,7 +10,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -39,7 +39,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index ba6f55d7..1086037d 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -21,10 +21,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return ""+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -32,7 +32,7 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb) { return _con._print(sb); } - + @Override public Type compute() { return _con; } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java index 01314c74..bf025453 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -16,7 +16,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -32,6 +32,6 @@ public class DivNode extends Node { @Override public Node idealize() { return null; } - + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 08924563..0d59ef25 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -7,15 +7,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java index 0f21f67a..2dbdd695 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -7,7 +7,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -34,16 +34,16 @@ public class MulNode extends Node { 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(); - + return null; } @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 81c90f68..7ba1aa19 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java index 810f931b..5412108b 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/Node.java @@ -74,9 +74,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -100,7 +100,7 @@ public abstract class Node { // Every Node implements this. abstract StringBuilder _print1(StringBuilder sb); - + /** * Gets the ith input node * @param i Offset of the input node @@ -117,19 +117,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -188,7 +188,7 @@ public abstract class Node { 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 @@ -211,7 +211,7 @@ public abstract class Node { public N unkeep() { delUse(null); return (N)this; } // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -236,7 +236,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -245,13 +245,13 @@ public abstract class Node { return deadCodeElim(new ConstantNode(type).peephole()); // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } @@ -271,7 +271,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -340,7 +340,7 @@ public abstract class Node { // ------------------------------------------------------------------------ // Peephole utilities - + // Swap inputs without letting either input go dead during the swap. Node swap12() { Node tmp = in(1); @@ -348,7 +348,7 @@ public abstract class Node { _inputs.set(2,tmp); return this; } - + // does this node contain all constants? // Ignores in(0), as is usually control. boolean allCons() { diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java index 5dd6a86e..e3683646 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -7,15 +7,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 716d03d9..374e1183 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -5,7 +5,7 @@ import com.seaofnodes.simple.type.Type; public class PhiNode extends Node { final String _label; - + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } @Override public String label() { return "Phi_"+_label; } @@ -36,7 +36,7 @@ public class PhiNode extends Node { return in(1); // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. + // Phi, but Phis do not make code. // Phi(op(A,B),op(Q,R),op(X,Y)) becomes // op(Phi(A,Q,X), Phi(B,R,Y)). Node op = in(1); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/ProjNode.java index b78a3e28..2826d192 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -10,7 +10,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index d0d3eb06..c9d6c9c8 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -19,7 +19,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -29,7 +29,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return new TypeTuple(ctrl()._type,expr()._type); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 543d0f83..32045fbc 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -28,8 +28,8 @@ public class ScopeNode extends Node { _scopes = new Stack<>(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -65,7 +65,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } @@ -152,7 +152,7 @@ public class ScopeNode extends Node { dup.addDef(in(i)); return dup; } - + /** * Merges the names whose node bindings differ, by creating Phi node for such names * The names could occur at all stack levels, but a given name can only differ in the diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/StartNode.java index e94c24e0..022e276d 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -13,7 +13,7 @@ import com.seaofnodes.simple.type.TypeTuple; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = new TypeTuple(args); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/StopNode.java index 9ec39373..a30256f9 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -28,7 +28,7 @@ public class StopNode extends Node { public ReturnNode ret() { return nIns()==1 ? (ReturnNode)in(0) : null; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java index 41d7d092..0d0ed7dc 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -16,7 +16,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter05/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter05/src/main/java/com/seaofnodes/simple/type/Type.java index 4cee0d3c..2179d3db 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 3037e45f..9fb4b853 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public boolean isTop() { return !_is_con && _con==0; } public boolean isBot() { return !_is_con && _con==1; } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if (isTop()) return sb.append("IntTop"); if (isBot()) return sb.append("IntBot"); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 3db639cf..33417eb2 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; public TypeTuple(Type... _types) { diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java index e914f7c5..97666d27 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -26,7 +26,7 @@ return a;"""); StopNode ret = parser.parse(true); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } - + @Test public void testChapter5Test() { Parser parser = new Parser( @@ -41,20 +41,20 @@ return c;""", TypeInteger.BOT); StopNode ret = parser.parse(true); assertEquals("return Phi(Region16,4,3);", ret.toString()); } - + @Test public void testChapter5Return2() { Parser parser = new Parser( """ -if( arg==1 ) - return 3; -else - return 4; +if( arg==1 ) + return 3; +else + return 4; #showGraph;""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } - + @Test public void testChapter5IfMergeB() { Parser parser = new Parser( @@ -140,50 +140,50 @@ return a;"""); StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } - + @Test public void testChapter5HalfDef() { - try { + 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 testChapter5HalfDef2() { - try { + 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 testChapter5BadNum() { - try { + try { new Parser("return 1-;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); } } - + @Test public void testChapter5Keyword1() { - try { + try { new Parser("int true=0; return true;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'true'",e.getMessage()); } } - + @Test public void testChapter5Keyword2() { - try { + try { new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); } catch( RuntimeException e ) { @@ -193,14 +193,14 @@ return a;"""); @Test public void testChapter5Keyword3() { - try { + try { new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected =: (",e.getMessage()); } } - + @Test public void testChapter4Peephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -228,7 +228,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testChapter4Peephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -242,7 +242,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testChapter4VarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -303,7 +303,7 @@ return a;"""); @Test public void testChapter4Bug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -317,7 +317,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -357,7 +357,7 @@ return a;"""); @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -426,7 +426,7 @@ return a;"""); 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 ) { @@ -449,7 +449,7 @@ return a;"""); @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -459,7 +459,7 @@ return a;"""); @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -475,7 +475,7 @@ return a;"""); @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter06/README.md b/chapter06/README.md index 6f7f3baa..cb265f5b 100644 --- a/chapter06/README.md +++ b/chapter06/README.md @@ -28,7 +28,7 @@ The entire revised Lattice is shown below: ![Lattice](./docs/lattice.svg) -## Peephole of `if` +## Peephole of `if` * Our general strategy is to continue parsing the entire `if` statement even if we know that one branch is dead. * This approach creates dead nodes which get cleaned up by dead code @@ -44,7 +44,7 @@ The entire revised Lattice is shown below: important observation is that *we __can__ kill the `If` node after __both__ the `Proj` nodes have been created*, because any subsequent control flow can only see the `Proj` nodes. -* To ensure this, we add a dummy user to `If` before creating the first `Proj` and remove it immediately after, allowing the +* To ensure this, we add a dummy user to `If` before creating the first `Proj` and remove it immediately after, allowing the second `Proj` to trigger a dead code elimination of the `If`. * The live `Proj` gets replaced by the parent of `If`, whereas the dead `Proj` gets replaced by "~Ctrl". * The parsing then continues as normal. @@ -52,7 +52,7 @@ The entire revised Lattice is shown below: * Again keeping with our strategy we merge as normal, including creating `Phi` nodes. * The `Region` may have have one of its inputs as "~Ctrl". This will be seen by the `Phi` which then optimizes itself. If the `Phi` has just one live - input, the `Phi` peephole replaces itself with the remaining input. + input, the `Phi` peephole replaces itself with the remaining input. * Finally, at the end, when the `Region` node is peepholed, we see that it has only one live input and no Phi uses and can be replaced with its one live input. @@ -154,28 +154,28 @@ outer `if`. In compiler parlance, we say that the outer `if` dominates the inner There is a generic way to determine this type of domination; it involves constructing a dominator tree. For details of how to do this, please refer to the paper `A Simple, Fast Dominance Algorithm`.[^1] -However, constructing a dominator tree is an expensive operation; one that we would not like to +However, constructing a dominator tree is an expensive operation; one that we would not like to do in the middle of parsing. Fortunately, since we do not have loops yet, we do not have back edges in our control flow graph. Our control flow graph is essentially hierarchical, tree like. Thus, a simple form of dominator identification is possible. The main idea is that we can assign a `depth` to each control node, this represents the node's depth in the hierarchy. We can compute this depth incrementally as we need to. The nodes that are deeper in the hierarchy -have a greater depth than the ones above. +have a greater depth than the ones above. -Secondly we know that for our control nodes, except for `Region`s, there is only one incoming control edge, +Secondly we know that for our control nodes, except for `Region`s, there is only one incoming control edge, which means that most control nodes have a single immediate dominating control node. `Region` nodes are different as they can have one or more control edges; but, we know that for `if` statements, a `Region` node has exactly two control inputs, one from each branch of the `if`. To find the immediate dominator of a `Region` node in the limited scenario of an `if`, we can follow the immediate dominator of each branch until we find the common ancestor node that dominates both the branches. -If this ancestor node is also an `if` and its predicate is the same as the current `if`, then we know that +If this ancestor node is also an `if` and its predicate is the same as the current `if`, then we know that the outcome of the present `if` is fully determined. The code that does this check is in the [`compute` method of the `if`](https://github.com/SeaOfNodes/Simple/blob/main/chapter06/src/main/java/com/seaofnodes/simple/node/IfNode.java#L38-L45). -The `idom` method in `Node` provides a default implementation of the depth based immediate dominator calculation. +The `idom` method in `Node` provides a default implementation of the depth based immediate dominator calculation. `Region`'s [override this](https://github.com/SeaOfNodes/Simple/blob/main/chapter06/src/main/java/com/seaofnodes/simple/node/RegionNode.java#L57-L74) to implement the more complex search described above. It turns out that to simplify the example above, we need a [further peephole in our `Add` node](https://github.com/SeaOfNodes/Simple/blob/main/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java#L64-L67). @@ -185,4 +185,4 @@ With all these changes, the outcome of the peephole is just what we would like t ![Graph3-NonConst-Peephole](./docs/06-graph3-nonconst-peephole.svg) [^1]: Cooper, K. D. Cooper, Harvey, T. J., and Kennedy, K. (2001). - A Simple, Fast Dominance Algorithm. \ No newline at end of file + A Simple, Fast Dominance Algorithm. diff --git a/chapter06/docs/06-grammar.md b/chapter06/docs/06-grammar.md index ea779a5a..4cf1cac6 100644 --- a/chapter06/docs/06-grammar.md +++ b/chapter06/docs/06-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | ifStatement | metaStatement @@ -43,15 +43,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -73,11 +73,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter06/docs/06-graph1-post.gv b/chapter06/docs/06-graph1-post.gv index 59214346..31480991 100644 --- a/chapter06/docs/06-graph1-post.gv +++ b/chapter06/docs/06-graph1-post.gv @@ -26,4 +26,4 @@ return 1; Stop3 -> Return13[taillabel=0 color=red]; Return13 -> Start2:p0[taillabel=0 color=red]; Return13 -> Con_12[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter06/docs/06-graph1-pre.gv b/chapter06/docs/06-graph1-pre.gv index b04fa2f9..2bfdd23d 100644 --- a/chapter06/docs/06-graph1-pre.gv +++ b/chapter06/docs/06-graph1-pre.gv @@ -49,4 +49,4 @@ return 1; Region14 -> If7:p1[taillabel=2 color=red]; Return16 -> Region14[taillabel=0 color=red]; Return16 -> Con_15[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter06/docs/06-graph2-post.gv b/chapter06/docs/06-graph2-post.gv index dacd80fd..61ae2ca1 100644 --- a/chapter06/docs/06-graph2-post.gv +++ b/chapter06/docs/06-graph2-post.gv @@ -30,4 +30,4 @@ return a; Return17 -> Start2:p0[taillabel=0 color=red]; Return17 -> Con_13[taillabel=1]; Stop3 -> Return17[taillabel=0 color=red]; -} \ No newline at end of file +} diff --git a/chapter06/docs/06-graph3-nonconst.gv b/chapter06/docs/06-graph3-nonconst.gv index 0746be85..7cd4e8e2 100644 --- a/chapter06/docs/06-graph3-nonconst.gv +++ b/chapter06/docs/06-graph3-nonconst.gv @@ -83,4 +83,4 @@ return a+b; Add24 -> Phi_a22[taillabel=2]; Return25 -> Region21[taillabel=0 color=red]; Return25 -> Add24[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter06/docs/06-graph3-true.gv b/chapter06/docs/06-graph3-true.gv index 8fdcc5df..9739169c 100644 --- a/chapter06/docs/06-graph3-true.gv +++ b/chapter06/docs/06-graph3-true.gv @@ -32,4 +32,4 @@ return a+b; Stop3 -> Return29[taillabel=0 color=red]; Return29 -> Start2:p0[taillabel=0 color=red]; Return29 -> Con_28[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter06/docs/build-graphs.sh b/chapter06/docs/build-graphs.sh index 44d2de3a..6b3f5bb2 100644 --- a/chapter06/docs/build-graphs.sh +++ b/chapter06/docs/build-graphs.sh @@ -10,4 +10,3 @@ dot -Tsvg 06-graph4-nonconst.gv > 06-graph4-nonconst.svg dot -Tsvg 06-graph4-true.gv > 06-graph4-true.svg dot -Tsvg 06-graph4-arg2.gv > 06-graph4-arg2.svg dot -Tsvg lattice.gv > lattice.svg - diff --git a/chapter06/docs/lattice.gv b/chapter06/docs/lattice.gv index 2cf27452..e533ad4a 100644 --- a/chapter06/docs/lattice.gv +++ b/chapter06/docs/lattice.gv @@ -61,4 +61,4 @@ digraph Lattice { INT_MAX; TUPLE} {rank=same; INT_BOT; CTRL} {rank=same; BOT;} -} \ No newline at end of file +} diff --git a/chapter06/pom.xml b/chapter06/pom.xml index 7a9b466d..fb852b3d 100644 --- a/chapter06/pom.xml +++ b/chapter06/pom.xml @@ -11,4 +11,4 @@ chapter06 jar Chapter 6 - \ No newline at end of file + diff --git a/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 704d4802..b30dd450 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -21,7 +21,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -35,10 +35,10 @@ public class GraphVisualizer { // Merge multiple edges hitting the same node. Makes common shared // nodes much prettier to look at. sb.append("\tconcentrate=\"true\";\n"); - + // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges for (ScopeNode sn: parser._xScopes) scopes(sb, sn); @@ -49,7 +49,7 @@ public class GraphVisualizer { // Walk the Scope edges for (ScopeNode sn: parser._xScopes) scopeEdges(sb, sn); - + sb.append("}\n"); return sb.toString(); } @@ -90,7 +90,7 @@ public class GraphVisualizer { } sb.append("\n"); sb.append("\t\t\t>\n\t\t"); - + } else { // control nodes have box shape // other nodes are ellipses, i.e. default shape @@ -102,7 +102,7 @@ public class GraphVisualizer { } sb.append("];\n"); } - + // Force Region & Phis to line up for( Node n : all ) { if( n instanceof RegionNode region ) { @@ -114,7 +114,7 @@ public class GraphVisualizer { sb.append("}\n"); } } - + sb.append("\t}\n"); // End Node cluster } @@ -179,7 +179,7 @@ public class GraphVisualizer { } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scopenode ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -202,7 +202,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java index dec0fa57..54c5b15c 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/Parser.java @@ -13,7 +13,7 @@ import java.util.*; * This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -54,7 +54,7 @@ public class Parser { add("true"); }}; - + /** * 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 @@ -75,12 +75,12 @@ public class Parser { @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); } @@ -101,7 +101,7 @@ public class Parser { return STOP; } - + /** * Parses a block * @@ -166,7 +166,7 @@ public class Parser { ctrl(ifT); // set ctrl token to ifTrue projection parseStatement(); // Parse true-side ScopeNode tScope = _scope; - + // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF); // Ctrl token is now set to ifFalse projection @@ -177,7 +177,7 @@ public class Parser { 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 @@ -466,7 +466,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -493,7 +493,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -509,7 +509,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/Utils.java b/chapter06/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java index 5ef9c94a..b37785f6 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -6,7 +6,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -15,7 +15,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+")); return sb.append(")"); } - + @Override public Type compute() { @@ -42,13 +42,13 @@ public class AddNode extends Node { // 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(); @@ -67,7 +67,7 @@ public class AddNode extends Node { return spline_cmp(lhs,rhs) ? swap12() : phiCon(this,true); // Now we only see (add add non) - + // Do we have (x + con1) + con2? // Replace with (x + (con1+con2) which then fold the constants if( lhs.in(2)._type.isConstant() && t2.isConstant() ) @@ -81,12 +81,12 @@ public class AddNode extends Node { if( phicon!=null ) return phicon; // Now we sort along the spline via rotates, to gather similar things together. - + // Do we rotate (x + y) + z // into (x + z) + y ? if( spline_cmp(lhs.in(2),rhs) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - + return null; } @@ -104,11 +104,11 @@ public class AddNode extends Node { lphi = pcon(lhs.in(2)); // Will rotate with the Phi push } if( lphi==null ) return null; - + // RHS is a constant or a Phi of constants if( !(rhs instanceof ConstantNode con) && pcon(rhs)==null ) return null; - + // If both are Phis, must be same Region if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) return null; @@ -130,7 +130,7 @@ public class AddNode extends Node { static PhiNode pcon(Node op) { return op instanceof PhiNode phi && phi.allCons() ? phi : null; } - + // Compare two off-spline nodes and decide what order they should be in. // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? // Generally constants always go right, then Phi-of-constants, then muls, then others. @@ -139,16 +139,16 @@ public class AddNode extends Node { static boolean spline_cmp( Node hi, Node lo ) { if( lo._type.isConstant() ) return false; if( hi._type.isConstant() ) return true ; - + if( lo instanceof PhiNode && lo.allCons() ) return false; if( hi instanceof PhiNode && hi.allCons() ) return true ; if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - + // Same category of "others" return lo._nid > hi._nid; } - + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 4eeef873..a9ec6838 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -10,7 +10,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -39,7 +39,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index ba6f55d7..1086037d 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -21,10 +21,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return ""+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -32,7 +32,7 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb) { return _con._print(sb); } - + @Override public Type compute() { return _con; } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java index 01314c74..bf025453 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -16,7 +16,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -32,6 +32,6 @@ public class DivNode extends Node { @Override public Node idealize() { return null; } - + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/IfNode.java index 1b0fdac7..34846e99 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -43,7 +43,7 @@ public class IfNode extends MultiNode { // Repeated test, dominated on one side. Test result is the same. ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE) : null; // Repeated test not dominated on one side - + return TypeTuple.IF_BOTH; } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 08924563..0d59ef25 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -7,15 +7,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java index 31683a4e..0380c079 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -7,7 +7,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -34,12 +34,12 @@ public class MulNode extends Node { 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(); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 81c90f68..7ba1aa19 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } } diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java index 4fbcf940..97ba68ab 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/Node.java @@ -46,7 +46,7 @@ public abstract class Node { */ public Type _type; - + /** * Immediate dominator tree depth, used to approximate a real IDOM during * parsing where we do not have the whole program, and also peepholes @@ -55,7 +55,7 @@ public abstract class Node { * See {@link ...} */ int _idepth; - + /** * 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. @@ -84,9 +84,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -110,7 +110,7 @@ public abstract class Node { // Every Node implements this. abstract StringBuilder _print1(StringBuilder sb); - + /** * Gets the ith input node * @param i Offset of the input node @@ -127,19 +127,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -160,7 +160,7 @@ public abstract class Node { // 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. @@ -209,7 +209,7 @@ public abstract class Node { 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 @@ -232,7 +232,7 @@ public abstract class Node { public N unkeep() { delUse(null); return (N)this; } // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -257,7 +257,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -266,13 +266,13 @@ public abstract class Node { return deadCodeElim(new ConstantNode(type).peephole()); // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } @@ -292,7 +292,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -361,7 +361,7 @@ public abstract class Node { // ------------------------------------------------------------------------ // Peephole utilities - + // Swap inputs without letting either input go dead during the swap. Node swap12() { Node tmp = in(1); @@ -369,7 +369,7 @@ public abstract class Node { _inputs.set(2,tmp); return this; } - + // does this node contain all constants? // Ignores in(0), as is usually control. boolean allCons() { diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java index 5dd6a86e..e3683646 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -7,15 +7,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(!")); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java index d435ea01..15088e08 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -5,7 +5,7 @@ import com.seaofnodes.simple.type.Type; public class PhiNode extends Node { final String _label; - + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } @Override public String label() { return "Phi_"+_label; } @@ -40,7 +40,7 @@ public class PhiNode extends Node { return live; // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. + // Phi, but Phis do not make code. // Phi(op(A,B),op(Q,R),op(X,Y)) becomes // op(Phi(A,Q,X), Phi(B,R,Y)). Node op = in(1); @@ -72,7 +72,7 @@ public class PhiNode extends Node { */ private Node singleUniqueInput() { Node live = null; - for( int i=1; i(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -65,7 +65,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } @@ -152,7 +152,7 @@ public class ScopeNode extends Node { dup.addDef(in(i)); return dup; } - + /** * Merges the names whose node bindings differ, by creating Phi node for such names * The names could occur at all stack levels, but a given name can only differ in the diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/StartNode.java index fd0f6c18..c5a45876 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -13,7 +13,7 @@ import com.seaofnodes.simple.type.TypeTuple; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = new TypeTuple(args); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/StopNode.java index c8056fbe..01574dc7 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -28,7 +28,7 @@ public class StopNode extends Node { public ReturnNode ret() { return nIns()==1 ? (ReturnNode)in(0) : null; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java index 41d7d092..0d0ed7dc 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -16,7 +16,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter06/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter06/src/main/java/com/seaofnodes/simple/type/Type.java index f88403a5..2f0e6171 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter06/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter06/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 93f5eae3..b30e21d8 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public boolean isTop() { return !_is_con && _con==0; } public boolean isBot() { return !_is_con && _con==1; } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if (isTop()) return sb.append("IntTop"); if (isBot()) return sb.append("IntBot"); diff --git a/chapter06/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter06/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index f8c3b3c8..0f8b95bd 100644 --- a/chapter06/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter06/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; public TypeTuple(Type... _types) { diff --git a/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java index d0e55cc7..fbafad2f 100644 --- a/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter06/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -309,7 +309,7 @@ return a;"""); StopNode ret = parser.parse(true); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } - + @Test public void testChapter5Test() { Parser parser = new Parser( @@ -324,20 +324,20 @@ return c;""", TypeInteger.BOT); StopNode ret = parser.parse(true); assertEquals("return Phi(Region16,4,3);", ret.toString()); } - + @Test public void testChapter5Return2() { Parser parser = new Parser( """ -if( arg==1 ) - return 3; -else - return 4; +if( arg==1 ) + return 3; +else + return 4; #showGraph;""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } - + @Test public void testChapter5IfMergeB() { Parser parser = new Parser( @@ -423,50 +423,50 @@ return a;"""); StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } - + @Test public void testChapter5HalfDef() { - try { + 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 testChapter5HalfDef2() { - try { + 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 testChapter5BadNum() { - try { + try { new Parser("return 1-;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); } } - + @Test public void testChapter5Keyword1() { - try { + try { new Parser("int true=0; return true;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'true'",e.getMessage()); } } - + @Test public void testChapter5Keyword2() { - try { + try { new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); } catch( RuntimeException e ) { @@ -476,14 +476,14 @@ return a;"""); @Test public void testChapter5Keyword3() { - try { + try { new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected =: (",e.getMessage()); } } - + @Test public void testChapter4Peephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -511,7 +511,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testChapter4Peephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -525,7 +525,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testChapter4VarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -586,7 +586,7 @@ return a;"""); @Test public void testChapter4Bug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -600,7 +600,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -640,7 +640,7 @@ return a;"""); @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -709,7 +709,7 @@ return a;"""); 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 ) { @@ -732,7 +732,7 @@ return a;"""); @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -742,7 +742,7 @@ return a;"""); @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -758,7 +758,7 @@ return a;"""); @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter07/README.md b/chapter07/README.md index 3e6a7692..03181d18 100644 --- a/chapter07/README.md +++ b/chapter07/README.md @@ -22,7 +22,7 @@ In general, we will rewrite the looping construct as follows: ```java loop_head: -if( !(arg < 10) ) +if( !(arg < 10) ) goto loop_exit; arg = arg + 1; goto loop_head; @@ -39,7 +39,7 @@ From an SSA[^1] point of view, since `arg` flows back, it requires a `phi` node // loop_head: arg2 = phi(arg1, arg3); -if( !(arg2 < 10) ) +if( !(arg2 < 10) ) goto loop_exit; arg3 = arg2 + 1; goto loop_head; @@ -47,11 +47,11 @@ goto loop_head; loop_exit: ``` -Notice that the phi for `arg2` refers to `arg3`, which is not known at the time we parse the `while` loop predicate. This is the crux of the problem that we need +Notice that the phi for `arg2` refers to `arg3`, which is not known at the time we parse the `while` loop predicate. This is the crux of the problem that we need to solve in order to successfully construct the Sea of Nodes graph, which is always in SSA form. Recall from [Chapter 5](../chapter05/README.md) that when parsing `if` statements, we clone the symbol tables as we go past the `if` predicate. -Later we merge the two sets of symbol tables at a `Region` - creating phis for all names that encountered a change in definition within the two +Later we merge the two sets of symbol tables at a `Region` - creating phis for all names that encountered a change in definition within the two branches of the `if` statement. In the case of the `if` statement, the phis are created at the merge point when we already know the definitions that are being merged. @@ -69,10 +69,10 @@ created until we complete parsing the loop body. This is because our phis are no ## Detailed Steps -1. We start by creating a new subclass of `Region`, the `Loop`. The `Loop` gets two control inputs, - the first is the entry point, i.e. the current binding to `$ctrl`, and second (`null`) is a placeholder for the back edge that is +1. We start by creating a new subclass of `Region`, the `Loop`. The `Loop` gets two control inputs, + the first is the entry point, i.e. the current binding to `$ctrl`, and second (`null`) is a placeholder for the back edge that is set after loop is parsed. The absence of a back edge is used as an indicator to switch off peepholes of the region and - associated phis. + associated phis. ```java ctrl(new LoopNode(ctrl(),null).peephole()); @@ -87,13 +87,13 @@ created until we complete parsing the loop body. This is because our phis are no // Make a new Scope for the body. _scope = _scope.dup(true); ``` - + Note that the `dup` call is given an argument `true`. This triggers creating phis. The code that creates the phis in the `dup()` method is shown below. ```java // boolean loop=true if this is a loop region - + String[] names = reverseNames(); // Get the variable names dup.add_def(ctrl()); // Control input is just copied for( int i=1; i'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -78,11 +78,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter07/pom.xml b/chapter07/pom.xml index 28d119b5..2f063952 100644 --- a/chapter07/pom.xml +++ b/chapter07/pom.xml @@ -11,4 +11,4 @@ chapter07 jar Chapter 7 - \ No newline at end of file + diff --git a/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 64ffc771..07f1af21 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -30,7 +30,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -50,7 +50,7 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges for( ScopeNode scope : parser._xScopes ) scope( sb, scope ); @@ -61,7 +61,7 @@ public class GraphVisualizer { // Walk the active Scope edges for( ScopeNode scope : parser._xScopes ) scopeEdges( sb, scope ); - + sb.append("}\n"); return sb.toString(); } @@ -195,13 +195,13 @@ public class GraphVisualizer { sb.append(" color=red"); // Backedges do not add a ranking constraint if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); + sb.append(" constraint=false"); sb.append("];\n"); } } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scope ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -226,7 +226,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java index 7cc52e78..349b2ce7 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java @@ -13,7 +13,7 @@ import java.util.*; * This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -55,7 +55,7 @@ public class Parser { 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 @@ -76,12 +76,12 @@ public class Parser { @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); } @@ -102,7 +102,7 @@ public class Parser { return STOP; } - + /** * Parses a block * @@ -236,7 +236,7 @@ public class Parser { ctrl(ifT); // set ctrl token to ifTrue projection parseStatement(); // Parse true-side ScopeNode tScope = _scope; - + // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF); // Ctrl token is now set to ifFalse projection @@ -247,7 +247,7 @@ public class Parser { 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 @@ -536,7 +536,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -563,7 +563,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -579,7 +579,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Utils.java b/chapter07/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java index d23fa7c5..8f3e5a98 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -8,7 +8,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -17,7 +17,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+"), visited); return sb.append(")"); } - + @Override public Type compute() { @@ -44,13 +44,13 @@ public class AddNode extends Node { // 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(); @@ -69,7 +69,7 @@ public class AddNode extends Node { return spline_cmp(lhs,rhs) ? swap12() : phiCon(this,true); // Now we only see (add add non) - + // Do we have (x + con1) + con2? // Replace with (x + (con1+con2) which then fold the constants if( lhs.in(2)._type.isConstant() && t2.isConstant() ) @@ -83,12 +83,12 @@ public class AddNode extends Node { if( phicon!=null ) return phicon; // Now we sort along the spline via rotates, to gather similar things together. - + // Do we rotate (x + y) + z // into (x + z) + y ? if( spline_cmp(lhs.in(2),rhs) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - + return null; } @@ -106,11 +106,11 @@ public class AddNode extends Node { lphi = pcon(lhs.in(2)); // Will rotate with the Phi push } if( lphi==null ) return null; - + // RHS is a constant or a Phi of constants if( !(rhs instanceof ConstantNode con) && pcon(rhs)==null ) return null; - + // If both are Phis, must be same Region if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) return null; @@ -132,7 +132,7 @@ public class AddNode extends Node { static PhiNode pcon(Node op) { return op instanceof PhiNode phi && phi.allCons() ? phi : null; } - + // Compare two off-spline nodes and decide what order they should be in. // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? // Generally constants always go right, then Phi-of-constants, then muls, then others. @@ -141,16 +141,16 @@ public class AddNode extends Node { static boolean spline_cmp( Node hi, Node lo ) { if( lo._type.isConstant() ) return false; if( hi._type.isConstant() ) return true ; - + if( lo instanceof PhiNode && lo.allCons() ) return false; if( hi instanceof PhiNode && hi.allCons() ) return true ; if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - + // Same category of "others" return lo._nid > hi._nid; } - + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java index d65fbf14..0c32d50d 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -12,7 +12,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -41,7 +41,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 0e13d2f2..13006bfb 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -23,10 +23,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return ""+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -34,9 +34,9 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb, BitSet visited) { return _con._print(sb); } - + @Override public boolean isMultiTail() { return true; } - + @Override public Type compute() { return _con; } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java index b0e12547..2213d2e7 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -18,7 +18,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -34,6 +34,6 @@ public class DivNode extends Node { @Override public Node idealize() { return null; } - + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java index e573010d..0f414c08 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -47,7 +47,7 @@ public class IfNode extends MultiNode { // Repeated test, dominated on one side. Test result is the same. ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE) : null; // Repeated test not dominated on one side - + return TypeTuple.IF_BOTH; } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java index aeb8a7f9..81f707ec 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -24,5 +24,5 @@ public class LoopNode extends RegionNode { return inProgress() ? null : super.idealize(); } - @Override Node idom() { return entry(); } + @Override Node idom() { return entry(); } } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8a96693d..3d944fd4 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -9,15 +9,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java index 70e6c0db..2e701a4e 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -9,7 +9,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -36,12 +36,12 @@ public class MulNode extends Node { 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(); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 81c90f68..7ba1aa19 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } } diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java index d9d9366f..35b07781 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java @@ -46,7 +46,7 @@ public abstract class Node { */ public Type _type; - + /** * Immediate dominator tree depth, used to approximate a real IDOM during * parsing where we do not have the whole program, and also peepholes @@ -55,7 +55,7 @@ public abstract class Node { * See {@link ...} */ int _idepth; - + /** * 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. @@ -84,9 +84,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -140,10 +140,10 @@ public abstract class Node { public boolean isMultiHead() { return false; } public boolean isMultiTail() { return false; } - + // ------------------------------------------------------------------------ // Graph Node & Edge manipulation - + /** * Gets the ith input node * @param i Offset of the input node @@ -160,19 +160,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -193,7 +193,7 @@ public abstract class Node { // 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. @@ -242,7 +242,7 @@ public abstract class Node { 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 @@ -276,10 +276,10 @@ public abstract class Node { } kill(); } - + // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -304,7 +304,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -313,13 +313,13 @@ public abstract class Node { return deadCodeElim(new ConstantNode(type).peephole()); // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } @@ -339,7 +339,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -408,7 +408,7 @@ public abstract class Node { // ------------------------------------------------------------------------ // Peephole utilities - + // Swap inputs without letting either input go dead during the swap. Node swap12() { Node tmp = in(1); @@ -416,7 +416,7 @@ public abstract class Node { _inputs.set(2,tmp); return this; } - + // does this node contain all constants? // Ignores in(0), as is usually control. boolean allCons() { diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java index 04e6c75b..39df410d 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,15 +9,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java index e6cf338b..2a4ff52a 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -7,7 +7,7 @@ import java.util.BitSet; public class PhiNode extends Node { final String _label; - + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } @Override public String label() { return "Phi_"+_label; } @@ -35,7 +35,7 @@ public class PhiNode extends Node { @Override public Type compute() { if( !(region() instanceof RegionNode r) || r.inProgress() ) - return Type.BOTTOM; + return Type.BOTTOM; Type t = Type.TOP; for (int i = 1; i < nIns(); i++) t = t.meet(in(i)._type); @@ -53,7 +53,7 @@ public class PhiNode extends Node { return live; // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. + // Phi, but Phis do not make code. // Phi(op(A,B),op(Q,R),op(X,Y)) becomes // op(Phi(A,Q,X), Phi(B,R,Y)). Node op = in(1); @@ -85,7 +85,7 @@ public class PhiNode extends Node { */ private Node singleUniqueInput() { Node live = null; - for( int i=1; i(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -59,7 +59,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } @@ -157,7 +157,7 @@ public class ScopeNode extends Node { } return dup; } - + /** * Merges the names whose node bindings differ, by creating Phi node for such names * The names could occur at all stack levels, but a given name can only differ in the @@ -176,7 +176,7 @@ public class ScopeNode extends Node { that.kill(); // Kill merged scope return r.unkeep().peephole(); } - + // Merge the backedge scope into this loop head scope // We set the second input to the phi from the back edge (i.e. loop body) public void endLoop(ScopeNode back, ScopeNode exit ) { diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java index 19d03e0f..01f1485a 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -15,7 +15,7 @@ import java.util.BitSet; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = new TypeTuple(args); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java index a0aacf2b..417790d9 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -30,7 +30,7 @@ public class StopNode extends Node { public ReturnNode ret() { return nIns()==1 ? (ReturnNode)in(0) : null; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java index 1484ab8a..4a06fcb3 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -18,7 +18,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java index f88403a5..2f0e6171 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 93f5eae3..b30e21d8 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public boolean isTop() { return !_is_con && _con==0; } public boolean isBot() { return !_is_con && _con==1; } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if (isTop()) return sb.append("IntTop"); if (isBot()) return sb.append("IntBot"); diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index f8c3b3c8..0f8b95bd 100644 --- a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; public TypeTuple(Type... _types) { diff --git a/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java index efcf90f5..9afcc49a 100644 --- a/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -41,7 +41,7 @@ return a; StopNode stop = parser.parse(true); assertEquals("return Phi(Region23,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString()); } - + @Test public void testChapter7WhileNested() { Parser parser = new Parser( @@ -540,7 +540,7 @@ return a;"""); StopNode ret = parser.parse(true); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } - + @Test public void testChapter5Test() { Parser parser = new Parser( @@ -555,20 +555,20 @@ return c;""", TypeInteger.BOT); StopNode ret = parser.parse(true); assertEquals("return Phi(Region16,4,3);", ret.toString()); } - + @Test public void testChapter5Return2() { Parser parser = new Parser( """ -if( arg==1 ) - return 3; -else - return 4; +if( arg==1 ) + return 3; +else + return 4; #showGraph;""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } - + @Test public void testChapter5IfMergeB() { Parser parser = new Parser( @@ -654,50 +654,50 @@ return a;"""); StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } - + @Test public void testChapter5HalfDef() { - try { + 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 testChapter5HalfDef2() { - try { + 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 testChapter5BadNum() { - try { + try { new Parser("return 1-;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); } } - + @Test public void testChapter5Keyword1() { - try { + try { new Parser("int true=0; return true;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'true'",e.getMessage()); } } - + @Test public void testChapter5Keyword2() { - try { + try { new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); } catch( RuntimeException e ) { @@ -707,14 +707,14 @@ return a;"""); @Test public void testChapter5Keyword3() { - try { + try { new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected =: (",e.getMessage()); } } - + @Test public void testChapter4Peephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -742,7 +742,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testChapter4Peephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -756,7 +756,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testChapter4VarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -817,7 +817,7 @@ return a;"""); @Test public void testChapter4Bug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -831,7 +831,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -871,7 +871,7 @@ return a;"""); @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -940,7 +940,7 @@ return a;"""); 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 ) { @@ -963,7 +963,7 @@ return a;"""); @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -973,7 +973,7 @@ return a;"""); @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -989,7 +989,7 @@ return a;"""); @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter08/README.md b/chapter08/README.md index 6d5ce026..4c095912 100644 --- a/chapter08/README.md +++ b/chapter08/README.md @@ -14,7 +14,7 @@ In [chapter 7](../chapter07/README.md), we created `PhiNode`s for all variables in the loop head eagerly. This approach creates some unnecessary Phis but the redundant Phis get cleaned up later. -In this chapter we add support for creating the `PhiNode`s lazily. +In this chapter we add support for creating the `PhiNode`s lazily. The main complication with creating `PhiNode`s lazily is that when we create a Phi, it must be created in the innermost loop head scope where it would have @@ -166,7 +166,7 @@ The implementation is a variation of above. return toScope; } ``` - + * The continue scope becomes the base scope for the subsequent `continue` statement, thus forming a stack of continue scopes/regions. * After the loop is done, if we find that a continue scope was created within @@ -266,7 +266,7 @@ We talk about resolving nodes above, but what does that mean? A node will either With that, a simple non-looping program can be evaluated. -Looping is almost functional with that, too, with only a couple of small extra details. Whenever control moves through a `LoopNode`, the evaluator checks that it hasn't run out of loop iterations. If it has, a timeout RuntimeException will be thrown. +Looping is almost functional with that, too, with only a couple of small extra details. Whenever control moves through a `LoopNode`, the evaluator checks that it hasn't run out of loop iterations. If it has, a timeout RuntimeException will be thrown. Finally, while calculating the value of `PhiNode`s in the loop region, we must be careful to compute all of their new values before we update any of their cached values. That's because as a loop iterates, we must ensure that all `PhiNode` values are consistently the value from the same point in time. Once all of the new values are calculated, we can update the cache for all of them at once. To illustrate this, consider the following code: @@ -292,5 +292,3 @@ return arg; ``` On the other hand, this engine is not designed to be particularly fast! Because not all expressions are cached, it's possible to construct programs like the preceeding one which have exponential runtime. - - diff --git a/chapter08/docs/08-grammar.md b/chapter08/docs/08-grammar.md index c603f335..4c61bfd7 100644 --- a/chapter08/docs/08-grammar.md +++ b/chapter08/docs/08-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | ifStatement | whileStatement @@ -26,10 +26,10 @@ whileStatement breakStatement : 'break' ';' ; - + continueStatement : 'continue' ';' - ; + ; ifStatement : 'if' '(' expression ')' statement ('else' statement)? @@ -58,15 +58,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -88,11 +88,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter08/pom.xml b/chapter08/pom.xml index 5942485f..738653fe 100644 --- a/chapter08/pom.xml +++ b/chapter08/pom.xml @@ -11,4 +11,4 @@ chapter08 jar Chapter 8 - \ No newline at end of file + diff --git a/chapter08/src/main/java/com/seaofnodes/simple/GraphEvaluator.java b/chapter08/src/main/java/com/seaofnodes/simple/GraphEvaluator.java index b89189f5..f5694a78 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/GraphEvaluator.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/GraphEvaluator.java @@ -1,181 +1,181 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; - -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashMap; - -public class GraphEvaluator { - - public enum ResultType { VALUE, FALLTHROUGH, TIMEOUT } - - public record Result(ResultType type, long value) {} - - /** - * 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 (node instanceof StartNode start) return start; - if( visit.get(node._nid) ) return null; - visit.set(node._nid); - for( Node def : node._inputs ) { - var res = findStart(visit,def); - if( res != null ) return res; - } - for( Node use : node._outputs ) { - var res = findStart(visit,use); - if( res != null ) return res; - } - return null; - } - - /** - * Find the control output from a control node - */ - private static Node findControl(Node control) { - for( Node use : control._outputs ) { - if (use.isCFG()) return use; - } - return null; - } - - /** - * Find the projection for a node - */ - private static ProjNode findProjection(Node node, int idx) { - for( Node use : node._outputs ) { - if (use instanceof ProjNode proj && proj._idx == idx) return proj; - } - return null; - } - - /** - * Cache values for phi and parameter projection nodes. - */ - private final HashMap cacheValues = new HashMap<>(); - /** - * Cache for loop phis as they can depend on itself or other loop phis - */ - private long[] loopPhiCache = new long[16]; - - private GraphEvaluator() { - } - - private long div(DivNode div) { - long in2 = getValue(div.in(2)); - return in2 == 0 ? 0 : getValue(div.in(1)) / in2; - } - - /** - * Calculate the value of a node - */ - private long getValue(Node node) { - var cache = cacheValues.get(node); - if (cache != null) return cache; - return switch (node) { - case ConstantNode cons -> ((TypeInteger)cons.compute()).value(); - case AddNode add -> getValue(add.in(1)) + getValue(add.in(2)); - case BoolNode.EQ eq -> getValue(eq.in(1)) == getValue(eq.in(2)) ? 1 : 0; - case BoolNode.LE le -> getValue(le.in(1)) <= getValue(le.in(2)) ? 1 : 0; - case BoolNode.LT lt -> getValue(lt.in(1)) < getValue(lt.in(2)) ? 1 : 0; - case DivNode div -> div(div); - case MinusNode minus -> -getValue(minus.in(1)); - case MulNode mul -> getValue(mul.in(1)) * getValue(mul.in(2)); - case NotNode not -> getValue(not.in(1)) == 0 ? 1 : 0; - case SubNode sub -> getValue(sub.in(1)) - getValue(sub.in(2)); - default -> throw Utils.TODO("Unexpected node " + node); - }; - } - - /** - * Special case of latchPhis when phis can depend on phis of the same region. - */ - private void latchLoopPhis(RegionNode region, Node prev) { - int idx = Utils.find(region._inputs, prev); - assert idx > 0; - int i = 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) { - var value = getValue(phi.in(idx)); - if (i == loopPhiCache.length) loopPhiCache = Arrays.copyOf(loopPhiCache, loopPhiCache.length*2); - loopPhiCache[i++] = value; - } - } - i = 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) cacheValues.put(phi, loopPhiCache[i++]); - } - } - - /** - * Calculate the values of phis of the region and caches the values. The phis are not allowed to depend on other phis of the region. - */ - private void latchPhis(RegionNode region, Node prev) { - int idx = Utils.find(region._inputs, prev); - assert idx > 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) { - var value = getValue(phi.in(idx)); - cacheValues.put(phi, value); - } - } - } - - /** - * Run the graph until either a return is found or the number of loop iterations are done. - */ - private Result evaluate(StartNode start, long parameter, int loops) { - var parameter1 = findProjection(start, 1); - if (parameter1 != null) cacheValues.put(parameter1, parameter); - Node control = findProjection(start, 0); - Node prev = start; - while (control != null) { - Node next; - switch (control) { - case RegionNode region -> { - if (region instanceof LoopNode && region.in(1) != prev) { - if (loops--<=0) return new Result(ResultType.TIMEOUT, 0); - latchLoopPhis(region, prev); - } else { - latchPhis(region, prev); - } - next = findControl(region); - } - case IfNode cond -> next = findProjection(cond, getValue(cond.in(1)) != 0 ? 0 : 1); - case ReturnNode ret -> { - return new Result(ResultType.VALUE, getValue(ret.in(1))); - } - case ProjNode ignored -> next = findControl(control); - default -> throw Utils.TODO("Unexpected control node " + control); - } - prev = control; - control = next; - } - return new Result(ResultType.FALLTHROUGH, 0); - } - - public static long evaluate(Node graph) { - return evaluate(graph, 0); - } - - public static long evaluate(Node graph, long parameter) { - return evaluate(graph, parameter, 1000); - } - - public static long evaluate(Node graph, long parameter, int loops) { - var res = evaluateWithResult(graph, parameter, loops); - if (res.type==ResultType.TIMEOUT) throw new RuntimeException("Timeout"); - return res.value; - } - - public static Result evaluateWithResult(Node graph, long parameter, int loops) { - var start = findStart(new BitSet(), graph); - if (start == null) return new Result(ResultType.TIMEOUT, 0); - var evaluator = new GraphEvaluator(); - return evaluator.evaluate(start, parameter, loops); - } - -} +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; + +public class GraphEvaluator { + + public enum ResultType { VALUE, FALLTHROUGH, TIMEOUT } + + public record Result(ResultType type, long value) {} + + /** + * 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 (node instanceof StartNode start) return start; + if( visit.get(node._nid) ) return null; + visit.set(node._nid); + for( Node def : node._inputs ) { + var res = findStart(visit,def); + if( res != null ) return res; + } + for( Node use : node._outputs ) { + var res = findStart(visit,use); + if( res != null ) return res; + } + return null; + } + + /** + * Find the control output from a control node + */ + private static Node findControl(Node control) { + for( Node use : control._outputs ) { + if (use.isCFG()) return use; + } + return null; + } + + /** + * Find the projection for a node + */ + private static ProjNode findProjection(Node node, int idx) { + for( Node use : node._outputs ) { + if (use instanceof ProjNode proj && proj._idx == idx) return proj; + } + return null; + } + + /** + * Cache values for phi and parameter projection nodes. + */ + private final HashMap cacheValues = new HashMap<>(); + /** + * Cache for loop phis as they can depend on itself or other loop phis + */ + private long[] loopPhiCache = new long[16]; + + private GraphEvaluator() { + } + + private long div(DivNode div) { + long in2 = getValue(div.in(2)); + return in2 == 0 ? 0 : getValue(div.in(1)) / in2; + } + + /** + * Calculate the value of a node + */ + private long getValue(Node node) { + var cache = cacheValues.get(node); + if (cache != null) return cache; + return switch (node) { + case ConstantNode cons -> ((TypeInteger)cons.compute()).value(); + case AddNode add -> getValue(add.in(1)) + getValue(add.in(2)); + case BoolNode.EQ eq -> getValue(eq.in(1)) == getValue(eq.in(2)) ? 1 : 0; + case BoolNode.LE le -> getValue(le.in(1)) <= getValue(le.in(2)) ? 1 : 0; + case BoolNode.LT lt -> getValue(lt.in(1)) < getValue(lt.in(2)) ? 1 : 0; + case DivNode div -> div(div); + case MinusNode minus -> -getValue(minus.in(1)); + case MulNode mul -> getValue(mul.in(1)) * getValue(mul.in(2)); + case NotNode not -> getValue(not.in(1)) == 0 ? 1 : 0; + case SubNode sub -> getValue(sub.in(1)) - getValue(sub.in(2)); + default -> throw Utils.TODO("Unexpected node " + node); + }; + } + + /** + * Special case of latchPhis when phis can depend on phis of the same region. + */ + private void latchLoopPhis(RegionNode region, Node prev) { + int idx = Utils.find(region._inputs, prev); + assert idx > 0; + int i = 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) { + var value = getValue(phi.in(idx)); + if (i == loopPhiCache.length) loopPhiCache = Arrays.copyOf(loopPhiCache, loopPhiCache.length*2); + loopPhiCache[i++] = value; + } + } + i = 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) cacheValues.put(phi, loopPhiCache[i++]); + } + } + + /** + * Calculate the values of phis of the region and caches the values. The phis are not allowed to depend on other phis of the region. + */ + private void latchPhis(RegionNode region, Node prev) { + int idx = Utils.find(region._inputs, prev); + assert idx > 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) { + var value = getValue(phi.in(idx)); + cacheValues.put(phi, value); + } + } + } + + /** + * Run the graph until either a return is found or the number of loop iterations are done. + */ + private Result evaluate(StartNode start, long parameter, int loops) { + var parameter1 = findProjection(start, 1); + if (parameter1 != null) cacheValues.put(parameter1, parameter); + Node control = findProjection(start, 0); + Node prev = start; + while (control != null) { + Node next; + switch (control) { + case RegionNode region -> { + if (region instanceof LoopNode && region.in(1) != prev) { + if (loops--<=0) return new Result(ResultType.TIMEOUT, 0); + latchLoopPhis(region, prev); + } else { + latchPhis(region, prev); + } + next = findControl(region); + } + case IfNode cond -> next = findProjection(cond, getValue(cond.in(1)) != 0 ? 0 : 1); + case ReturnNode ret -> { + return new Result(ResultType.VALUE, getValue(ret.in(1))); + } + case ProjNode ignored -> next = findControl(control); + default -> throw Utils.TODO("Unexpected control node " + control); + } + prev = control; + control = next; + } + return new Result(ResultType.FALLTHROUGH, 0); + } + + public static long evaluate(Node graph) { + return evaluate(graph, 0); + } + + public static long evaluate(Node graph, long parameter) { + return evaluate(graph, parameter, 1000); + } + + public static long evaluate(Node graph, long parameter, int loops) { + var res = evaluateWithResult(graph, parameter, loops); + if (res.type==ResultType.TIMEOUT) throw new RuntimeException("Timeout"); + return res.value; + } + + public static Result evaluateWithResult(Node graph, long parameter, int loops) { + var start = findStart(new BitSet(), graph); + if (start == null) return new Result(ResultType.TIMEOUT, 0); + var evaluator = new GraphEvaluator(); + return evaluator.evaluate(start, parameter, loops); + } + +} diff --git a/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 5009264f..d75e5bea 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -30,7 +30,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(parser.src()); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -50,7 +50,7 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges for( ScopeNode scope : parser._xScopes ) scope( sb, scope ); @@ -61,7 +61,7 @@ public class GraphVisualizer { // Walk the active Scope edges for( ScopeNode scope : parser._xScopes ) scopeEdges( sb, scope ); - + sb.append("}\n"); return sb.toString(); } @@ -195,13 +195,13 @@ public class GraphVisualizer { sb.append(" color=red"); // Backedges do not add a ranking constraint if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); + sb.append(" constraint=false"); sb.append("];\n"); } } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scope ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -228,7 +228,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java index 45332396..d4b0c327 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java @@ -13,7 +13,7 @@ import java.util.*; * This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -57,7 +57,7 @@ public class Parser { 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 @@ -82,12 +82,12 @@ public class Parser { @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); } @@ -108,7 +108,7 @@ public class Parser { return STOP; } - + /** * Parses a block * @@ -199,7 +199,7 @@ public class Parser { // No continues yet _continueScope = null; - + // Parse the true side, which corresponds to loop body // Our current scope is the body Scope ctrl(ifT); // set ctrl token to ifTrue projection @@ -283,7 +283,7 @@ public class Parser { ctrl(ifT); // set ctrl token to ifTrue projection parseStatement(); // Parse true-side ScopeNode tScope = _scope; - + // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF); // Ctrl token is now set to ifFalse projection @@ -294,7 +294,7 @@ public class Parser { 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 @@ -583,7 +583,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -610,7 +610,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -626,7 +626,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Utils.java b/chapter08/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java index 5878f340..84a368fd 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -8,7 +8,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -17,7 +17,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+"), visited); return sb.append(")"); } - + @Override public Type compute() { @@ -44,13 +44,13 @@ public class AddNode extends Node { // 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(); @@ -74,7 +74,7 @@ public class AddNode extends Node { // 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 if( lhs.in(2)._type.isConstant() && t2.isConstant() ) @@ -88,12 +88,12 @@ public class AddNode extends Node { if( phicon!=null ) return phicon; // Now we sort along the spline via rotates, to gather similar things together. - + // Do we rotate (x + y) + z // into (x + z) + y ? if( spline_cmp(lhs.in(2),rhs) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); - + return null; } @@ -111,11 +111,11 @@ public class AddNode extends Node { lphi = pcon(lhs.in(2)); // Will rotate with the Phi push } if( lphi==null ) return null; - + // RHS is a constant or a Phi of constants if( !(rhs instanceof ConstantNode con) && pcon(rhs)==null ) return null; - + // If both are Phis, must be same Region if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) return null; @@ -137,7 +137,7 @@ public class AddNode extends Node { static PhiNode pcon(Node op) { return op instanceof PhiNode phi && phi.allCons() ? phi : null; } - + // Compare two off-spline nodes and decide what order they should be in. // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? // Generally constants always go right, then Phi-of-constants, then muls, then others. @@ -146,16 +146,16 @@ public class AddNode extends Node { static boolean spline_cmp( Node hi, Node lo ) { if( lo._type.isConstant() ) return false; if( hi._type.isConstant() ) return true ; - + if( lo instanceof PhiNode && lo.allCons() ) return false; if( hi instanceof PhiNode && hi.allCons() ) return true ; if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - + // Same category of "others" return lo._nid > hi._nid; } - + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java index d65fbf14..0c32d50d 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -12,7 +12,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -41,7 +41,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 0e13d2f2..13006bfb 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -23,10 +23,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return ""+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -34,9 +34,9 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb, BitSet visited) { return _con._print(sb); } - + @Override public boolean isMultiTail() { return true; } - + @Override public Type compute() { return _con; } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java index b0e12547..2213d2e7 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -18,7 +18,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -34,6 +34,6 @@ public class DivNode extends Node { @Override public Node idealize() { return null; } - + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java index 1d1da4fa..23a8fafc 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -47,7 +47,7 @@ public class IfNode extends MultiNode { // Repeated test, dominated on one side. Test result is the same. ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE) : dom._type; // Repeated test not dominated on one side - + return TypeTuple.IF_BOTH; } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 0b84f897..c192bc20 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -25,5 +25,5 @@ public class LoopNode extends RegionNode { return inProgress() ? null : super.idealize(); } - @Override Node idom() { return entry(); } + @Override Node idom() { return entry(); } } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8a96693d..3d944fd4 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -9,15 +9,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java index 70e6c0db..2e701a4e 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -9,7 +9,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -36,12 +36,12 @@ public class MulNode extends Node { 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(); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 81c90f68..7ba1aa19 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java index fd312c3d..2d6d5ad5 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java @@ -47,7 +47,7 @@ public abstract class Node { */ public Type _type; - + /** * Immediate dominator tree depth, used to approximate a real IDOM during * parsing where we do not have the whole program, and also peepholes @@ -56,7 +56,7 @@ public abstract class Node { * See {@link ...} */ int _idepth; - + /** * 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. @@ -85,9 +85,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -101,7 +101,7 @@ public abstract class Node { public final String print() { return _print0(new StringBuilder(), new BitSet()).toString(); } - + // This is the common print: check for repeats, check for DEAD and print // "DEAD" else call the per-Node print1. final StringBuilder _print0(StringBuilder sb, BitSet visited) { @@ -141,13 +141,13 @@ public abstract class Node { } public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - + public boolean isMultiHead() { return false; } public boolean isMultiTail() { return false; } - + // ------------------------------------------------------------------------ // Graph Node & Edge manipulation - + /** * Gets the ith input node * @param i Offset of the input node @@ -162,19 +162,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -195,7 +195,7 @@ public abstract class Node { // 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. @@ -244,7 +244,7 @@ public abstract class Node { 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 @@ -278,10 +278,10 @@ public abstract class Node { } kill(); } - + // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -306,7 +306,7 @@ public abstract class Node { public final Node peephole( ) { // Compute initial or improved Type Type type = _type = compute(); - + if (_disablePeephole) return this; // Peephole optimizations turned off @@ -315,13 +315,13 @@ public abstract class Node { return deadCodeElim(new ConstantNode(type).peephole()); // Future chapter: Global Value Numbering goes here - + // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed // Recursively optimize return deadCodeElim(n.peephole()); - + return this; // No progress } @@ -341,7 +341,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -410,7 +410,7 @@ public abstract class Node { // ------------------------------------------------------------------------ // Peephole utilities - + // Swap inputs without letting either input go dead during the swap. Node swap12() { Node tmp = in(1); @@ -418,7 +418,7 @@ public abstract class Node { _inputs.set(2,tmp); return this; } - + // does this node contain all constants? // Ignores in(0), as is usually control. boolean allCons() { diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java index 04e6c75b..39df410d 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,15 +9,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 975630d0..c7b5b357 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -7,7 +7,7 @@ import java.util.BitSet; public class PhiNode extends Node { final String _label; - + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } @Override public String label() { return "Phi_"+_label; } @@ -35,7 +35,7 @@ public class PhiNode extends Node { @Override public Type compute() { if( !(region() instanceof RegionNode r) || r.inProgress() ) - return Type.BOTTOM; + return Type.BOTTOM; Type t = Type.TOP; for (int i = 1; i < nIns(); i++) t = t.meet(in(i)._type); @@ -53,7 +53,7 @@ public class PhiNode extends Node { return live; // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. + // Phi, but Phis do not make code. // Phi(op(A,B),op(Q,R),op(X,Y)) becomes // op(Phi(A,Q,X), Phi(B,R,Y)). Node op = in(1); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 79ba9d13..c263502d 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -12,7 +12,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 00c010b2..453d028f 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -9,7 +9,7 @@ import java.util.BitSet; public class RegionNode extends Node { public RegionNode(Node... nodes) { super(nodes); } - + @Override public String label() { return "Region"; } diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 4dea773a..312058b9 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -21,7 +21,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -31,7 +31,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return new TypeTuple(ctrl()._type,expr()._type); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index a6a7f536..e5d03d93 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -28,8 +28,8 @@ public class ScopeNode extends Node { _scopes = new Stack<>(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -63,7 +63,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } @@ -190,7 +190,7 @@ public class ScopeNode extends Node { that.kill(); // Kill merged scope return r.unkeep().peephole(); } - + // Merge the backedge scope into this loop head scope // We set the second input to the phi from the back edge (i.e. loop body) public void endLoop(ScopeNode back, ScopeNode exit ) { diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java index 19d03e0f..01f1485a 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -15,7 +15,7 @@ import java.util.BitSet; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = new TypeTuple(args); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java index a0aacf2b..417790d9 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -30,7 +30,7 @@ public class StopNode extends Node { public ReturnNode ret() { return nIns()==1 ? (ReturnNode)in(0) : null; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java index 1484ab8a..4a06fcb3 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -18,7 +18,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java index f88403a5..2f0e6171 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java @@ -4,7 +4,7 @@ package com.seaofnodes.simple.type; /** * 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 @@ -15,7 +15,7 @@ package com.seaofnodes.simple.type; * 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 { diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 93f5eae3..b30e21d8 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public boolean isTop() { return !_is_con && _con==0; } public boolean isBot() { return !_is_con && _con==1; } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if (isTop()) return sb.append("IntTop"); if (isBot()) return sb.append("IntBot"); diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index f8c3b3c8..0f8b95bd 100644 --- a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; public TypeTuple(Type... _types) { diff --git a/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 3df49f4a..f8bbfaa3 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -154,10 +154,10 @@ return arg; StopNode stop = parser.parse(true); assertEquals("Stop[ return 0; return 0; ]", stop.toString()); } - + @Test public void testChapter8BreakOutsideLoop() { - try { + try { new Parser(""" if(arg <= 10) { break; @@ -182,7 +182,7 @@ return arg; StopNode stop = parser.parse(true); assertEquals("return arg;", stop.toString()); } - + @Test public void testChapter8Regress4() { Parser parser = new Parser(""" @@ -198,9 +198,9 @@ return a; """); StopNode stop = parser.parse(true); assertEquals("return Phi(Region28,Phi(Loop7,1,(Phi_a+1)),Add);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof RegionNode); + assertTrue(stop.ret().ctrl() instanceof RegionNode); } - + @Test public void testChapter8Regress5() { Parser parser = new Parser(""" @@ -216,7 +216,7 @@ return a; assertEquals("return (Phi(Loop7,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } - + @Test public void testChapter7Example() { Parser parser = new Parser( @@ -250,7 +250,7 @@ return a; StopNode stop = parser.parse(true); assertEquals("return Phi(Region22,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString()); } - + @Test public void testChapter7WhileNested() { Parser parser = new Parser( @@ -749,7 +749,7 @@ return a;"""); StopNode ret = parser.parse(true); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } - + @Test public void testChapter5Test() { Parser parser = new Parser( @@ -764,20 +764,20 @@ return c;""", TypeInteger.BOT); StopNode ret = parser.parse(true); assertEquals("return Phi(Region16,4,3);", ret.toString()); } - + @Test public void testChapter5Return2() { Parser parser = new Parser( """ -if( arg==1 ) - return 3; -else - return 4; +if( arg==1 ) + return 3; +else + return 4; #showGraph;""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } - + @Test public void testChapter5IfMergeB() { Parser parser = new Parser( @@ -863,20 +863,20 @@ return a;"""); StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } - + @Test public void testChapter5HalfDef() { - try { + 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 testChapter5HalfDef2() { - try { + try { new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); fail(); } catch( RuntimeException e ) { @@ -886,38 +886,38 @@ return a;"""); @Test public void testChapter5Regress1() { - try { + 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 testChapter5BadNum() { - try { + try { new Parser("return 1-;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); } } - + @Test public void testChapter5Keyword1() { - try { + try { new Parser("int true=0; return true;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'true'",e.getMessage()); } } - + @Test public void testChapter5Keyword2() { - try { + try { new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); } catch( RuntimeException e ) { @@ -927,14 +927,14 @@ return a;"""); @Test public void testChapter5Keyword3() { - try { + try { new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected =: (",e.getMessage()); } } - + @Test public void testChapter4Peephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -962,7 +962,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testChapter4Peephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -976,7 +976,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testChapter4VarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -1037,7 +1037,7 @@ return a;"""); @Test public void testChapter4Bug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -1051,7 +1051,7 @@ return a;"""); StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -1091,7 +1091,7 @@ return a;"""); @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -1160,7 +1160,7 @@ return a;"""); 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 ) { @@ -1183,7 +1183,7 @@ return a;"""); @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -1193,7 +1193,7 @@ return a;"""); @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { @@ -1209,7 +1209,7 @@ return a;"""); @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter08/src/test/java/com/seaofnodes/simple/Fuzzer08Test.java b/chapter08/src/test/java/com/seaofnodes/simple/Fuzzer08Test.java index b8c0a8e5..ee635c22 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/Fuzzer08Test.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/Fuzzer08Test.java @@ -1,26 +1,26 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.fuzzer.Fuzzer; -import org.junit.Ignore; -import org.junit.Test; - - -import static org.junit.Assert.assertTrue; - -/** - * To use the fuzzer run the fuzzPeeps methods. - * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line - * via mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps - */ -public class Fuzzer08Test { - - @Test - @Ignore - public void fuzzPeeps() { - var fuzzer = new Fuzzer(); - for (int i=0; i<100000; i++) - fuzzer.fuzzPeeps(i); - assertTrue(fuzzer.noExceptions()); - } - -} +package com.seaofnodes.simple; + +import com.seaofnodes.simple.fuzzer.Fuzzer; +import org.junit.Ignore; +import org.junit.Test; + + +import static org.junit.Assert.assertTrue; + +/** + * To use the fuzzer run the fuzzPeeps methods. + * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line + * via mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps + */ +public class Fuzzer08Test { + + @Test + @Ignore + public void fuzzPeeps() { + var fuzzer = new Fuzzer(); + for (int i=0; i<100000; i++) + fuzzer.fuzzPeeps(i); + assertTrue(fuzzer.noExceptions()); + } + +} diff --git a/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 2af22f1d..5fcd8764 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -1,128 +1,128 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.GraphEvaluator; -import com.seaofnodes.simple.node.StopNode; - -import java.util.ArrayList; -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 the compiled graph with and without peeps is 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) { - for (var ex : exceptions) { - if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; - } - exceptions.add(e); - System.out.println("========== Stack =========="); - e.printStackTrace(System.out); - 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(); - } - - /** - * Check that two graphs result in the same output when supplied with a value - * @param stop1 Stop node of the first graph - * @param stop2 Stop node of the second graph - * @param in The input value to both graphs to test for an equal output - */ - private static void checkGraphs(StopNode stop1, StopNode stop2, long in) { - var e1 = GraphEvaluator.evaluateWithResult(stop1, in, EVAL_TIMEOUT); - var e2 = GraphEvaluator.evaluateWithResult(stop2, in, EVAL_TIMEOUT); - if (e1.type() == GraphEvaluator.ResultType.TIMEOUT || e2.type() == GraphEvaluator.ResultType.TIMEOUT) return; - if (e1.type() != e2.type()) - throw new RuntimeException("Different calculations types " + e1.type() + " vs " + e2.type()); - if (e1.value() != e2.value()) - throw new RuntimeException("Different calculations values " + e1.value() + " vs " + e2.value()); - } - - /** - * Run checks for script. Compile the script with peeps enabled and disabled. - * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid. - * If the script was successfully parsed check that both version behave the same. - * @param script The script to test - * @param valid If the script is definitely valid. If not some exceptions may be suppressed. - */ - private static void runCheck(String script, boolean valid) { - StopNode stop1; - try { - stop1 = FuzzerUtils.parse(script, false); - } catch (RuntimeException e1) { - try { - FuzzerUtils.parse(script, true); - } catch (RuntimeException e2) { - if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { - if (!valid || e1.getClass() == RuntimeException.class) return; - } else { - e1.addSuppressed(e2); - } - } - throw e1; - } - var stop2 = FuzzerUtils.parse(script, true); - checkGraphs(stop1, stop2, 0); - checkGraphs(stop1, stop2, 1); - checkGraphs(stop1, stop2, 10); - } - - /** - * Check that the script does not result in exceptions. - * @param script The script to test. - * @param valid If the script is valid or if it might contain syntax errors. - */ - private void check(String script, boolean valid) { - try { - runCheck(script, valid); - } catch (Throwable e) { - recordException(e, script, s->runCheck(s, valid)); - } - } - - /** - * 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, false).genProgram(); - check(sb.toString(), valid); - } - - /** - * Check that no exceptions happened. - * @return true if no exception happened - */ - public boolean noExceptions() { - return exceptions.isEmpty(); - } -} +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.GraphEvaluator; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +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 the compiled graph with and without peeps is 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) { + for (var ex : exceptions) { + if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; + } + exceptions.add(e); + System.out.println("========== Stack =========="); + e.printStackTrace(System.out); + 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(); + } + + /** + * Check that two graphs result in the same output when supplied with a value + * @param stop1 Stop node of the first graph + * @param stop2 Stop node of the second graph + * @param in The input value to both graphs to test for an equal output + */ + private static void checkGraphs(StopNode stop1, StopNode stop2, long in) { + var e1 = GraphEvaluator.evaluateWithResult(stop1, in, EVAL_TIMEOUT); + var e2 = GraphEvaluator.evaluateWithResult(stop2, in, EVAL_TIMEOUT); + if (e1.type() == GraphEvaluator.ResultType.TIMEOUT || e2.type() == GraphEvaluator.ResultType.TIMEOUT) return; + if (e1.type() != e2.type()) + throw new RuntimeException("Different calculations types " + e1.type() + " vs " + e2.type()); + if (e1.value() != e2.value()) + throw new RuntimeException("Different calculations values " + e1.value() + " vs " + e2.value()); + } + + /** + * Run checks for script. Compile the script with peeps enabled and disabled. + * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid. + * If the script was successfully parsed check that both version behave the same. + * @param script The script to test + * @param valid If the script is definitely valid. If not some exceptions may be suppressed. + */ + private static void runCheck(String script, boolean valid) { + StopNode stop1; + try { + stop1 = FuzzerUtils.parse(script, false); + } catch (RuntimeException e1) { + try { + FuzzerUtils.parse(script, true); + } catch (RuntimeException e2) { + if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { + if (!valid || e1.getClass() == RuntimeException.class) return; + } else { + e1.addSuppressed(e2); + } + } + throw e1; + } + var stop2 = FuzzerUtils.parse(script, true); + checkGraphs(stop1, stop2, 0); + checkGraphs(stop1, stop2, 1); + checkGraphs(stop1, stop2, 10); + } + + /** + * Check that the script does not result in exceptions. + * @param script The script to test. + * @param valid If the script is valid or if it might contain syntax errors. + */ + private void check(String script, boolean valid) { + try { + runCheck(script, valid); + } catch (Throwable e) { + recordException(e, script, s->runCheck(s, valid)); + } + } + + /** + * 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, false).genProgram(); + check(sb.toString(), valid); + } + + /** + * Check that no exceptions happened. + * @return true if no exception happened + */ + public boolean noExceptions() { + return exceptions.isEmpty(); + } +} diff --git a/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index ad499836..37a57e11 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -1,58 +1,58 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -/** - * Some utilities for the fuzzer. - */ -class FuzzerUtils { - - /** - * Check that eiter one of the objects is null or that both are equal. - */ - private static boolean equalOrNull(Object a, Object b) { - if (a == null || b == null) return true; - 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 (!equalOrNull(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=(])--"), ""), - new Replace(Pattern.compile("\\b--"), "+"), - new Replace(Pattern.compile("\\b\\+-"), "-"), - new Replace(Pattern.compile("\\bfalse\\b"), "0"), - new Replace(Pattern.compile("\\btrue\\b"), "1"), - new Replace(Pattern.compile("-"), ""), - new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), - new Replace(Pattern.compile("\\{(?:[^{}]|\\{})+}"), "{}"), - new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)}"), "$1"), - new Replace(Pattern.compile("\\(([^()]+)\\)"), "$1"), - new Replace(Pattern.compile("\\b\\w+\\b"), "1"), - new Replace(Pattern.compile("\\b\\w+\\b"), "0"), - new Replace(Pattern.compile("\\d+\\+"), ""), - new Replace(Pattern.compile("\\+\\d+"), ""), - new Replace(Pattern.compile("(?<=\\n|^)[^\\n]*(?=\\n|$)"), "\n"), - new Replace(Pattern.compile("\\n+"), "\n"), - new Replace(Pattern.compile("^\\n+|\\n+$"), ""), - new Replace(Pattern.compile("else *"), ""), - new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), - }; - - /** - * 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, String with) { - var matcher = pattern.matcher(script); - var sb = new StringBuilder(); - int last = 0; - while (matcher.find()) { - var pos = sb.length(); - matcher.appendReplacement(sb, with); - var tail = sb.length(); - matcher.appendTail(sb); - if (tester.test(sb.toString())) { - sb.setLength(tail); - } else { - sb.setLength(pos); - sb.append(script, last, matcher.end()); - } - last = matcher.end(); - } - matcher.appendTail(sb); - return sb.toString(); - } - - /** - * Clean up variable names from the random ones generated by the fuzzer. - */ - private String cleanVariables(String script) { - var matcher = Pattern.compile("\\bint ([0-9a-zA-Z_]+)").matcher(script); - int num = 0; - while (matcher.find()) { - var var = matcher.group(1); - if (var.matches("v\\d+")) continue; - while (script.contains("v"+num)) num++; - var n = script.replaceAll("\\b"+var+"\\b", "v" + num); - if (tester.test(n)) { - script = n; - } - } - return script; - } - - /** - * Run all rewrite rules once. - */ - private String doAllReplacements(String script) { - for (var replace: REPLACEMENTS) { - script = doReplacement(script, replace.p, replace.r); - } - 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); - }); - } - -} +package com.seaofnodes.simple.fuzzer; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * This class is used to reduce test cases created by the fuzzer. + * It applies rewrite rules to the test case and checks that the + * test still produces the same exception. + * This is done in a fixpoint iteration until every rewrite produces + * no or a different exception. + */ +public class Reducer { + + /** + * Holder for the rewrite rules + */ + private record Replace(Pattern p, String r) {} + + /** + * List of rewrite rules. + */ + private static final Replace[] REPLACEMENTS = { + new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), + new Replace(Pattern.compile("\\b--"), "+"), + new Replace(Pattern.compile("\\b\\+-"), "-"), + new Replace(Pattern.compile("\\bfalse\\b"), "0"), + new Replace(Pattern.compile("\\btrue\\b"), "1"), + new Replace(Pattern.compile("-"), ""), + new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), + new Replace(Pattern.compile("\\{(?:[^{}]|\\{})+}"), "{}"), + new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)}"), "$1"), + new Replace(Pattern.compile("\\(([^()]+)\\)"), "$1"), + new Replace(Pattern.compile("\\b\\w+\\b"), "1"), + new Replace(Pattern.compile("\\b\\w+\\b"), "0"), + new Replace(Pattern.compile("\\d+\\+"), ""), + new Replace(Pattern.compile("\\+\\d+"), ""), + new Replace(Pattern.compile("(?<=\\n|^)[^\\n]*(?=\\n|$)"), "\n"), + new Replace(Pattern.compile("\\n+"), "\n"), + new Replace(Pattern.compile("^\\n+|\\n+$"), ""), + new Replace(Pattern.compile("else *"), ""), + new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), + }; + + /** + * 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, String with) { + var matcher = pattern.matcher(script); + var sb = new StringBuilder(); + int last = 0; + while (matcher.find()) { + var pos = sb.length(); + matcher.appendReplacement(sb, with); + var tail = sb.length(); + matcher.appendTail(sb); + if (tester.test(sb.toString())) { + sb.setLength(tail); + } else { + sb.setLength(pos); + sb.append(script, last, matcher.end()); + } + last = matcher.end(); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * Clean up variable names from the random ones generated by the fuzzer. + */ + private String cleanVariables(String script) { + var matcher = Pattern.compile("\\bint ([0-9a-zA-Z_]+)").matcher(script); + int num = 0; + while (matcher.find()) { + var var = matcher.group(1); + if (var.matches("v\\d+")) continue; + while (script.contains("v"+num)) num++; + var n = script.replaceAll("\\b"+var+"\\b", "v" + num); + if (tester.test(n)) { + script = n; + } + } + return script; + } + + /** + * Run all rewrite rules once. + */ + private String doAllReplacements(String script) { + for (var replace: REPLACEMENTS) { + script = doReplacement(script, replace.p, replace.r); + } + 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/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 9df3dfcc..148c2319 100644 --- a/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter08/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -1,462 +1,462 @@ -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; - -/** - * 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 { - - /** - * Get the list of keywords from the parser. - */ - @SuppressWarnings("unchecked") - private static HashSet getKeywords() { - try { - var field = Parser.class.getDeclaredField("KEYWORDS"); - field.setAccessible(true); - return (HashSet) field.get(new Parser("")); - } catch (Throwable e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * 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 = getKeywords(); - - /** - * 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 = {"-"}; - - - /** - * 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 = 7; - /** - * The depth of expressions allowed. - */ - private int exprDepth = 3; - /** - * Current variables in scope. - */ - private final ArrayList variables = new ArrayList<>(); - /** - * If this needs to generate valid scripts or is also allowed to generate invalid ones. - */ - private final 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< 0 && random.nextInt(10) > 7) { - // Use a variable outside the current scope. - var v = variables.get(random.nextInt(currScopeStart)); - if (variables.lastIndexOf(v) < currScopeStart) return v; - if (!generateValid) { - maybeInvalid = true; - return v; - } - } - // Generate a new random variable name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - - - /** - * 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() { - this.maybeInvalid = false; - variables.add(ScopeNode.ARG0); - currScopeStart = variables.size(); - if ((genStatements() & FLAG_STOP) == 0) { - if (generateValid || random.nextInt(10)<7) { - genReturn(); - } else { - this.maybeInvalid = true; - } - } - 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(10); - for (int i=0; i genBlock(); - case 2 -> genIf(); - case 3 -> genWhile(); - case 4, 5 -> genDecl(); - case 6, 7, 8 -> genAssignment(); - default -> genExit(); - }; - } - - /** - * Generate a statement for if, else and while blocks. - * This is special since it disallows declarations and prefers to generate blocks. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement - */ - public int genStatementBlock() { - indentation += INDENTATION; - int res; - if (depth == 0) { - res = random.nextBoolean() ? genAssignment() : genExit(); - } else { - depth--; - res = switch (random.nextInt(10)) { - case 1, 2, 3, 4, 5 -> { - depth++; - indentation -= INDENTATION; - var ret = genBlock(); - indentation += INDENTATION; - depth--; - yield ret; - } - case 6 -> genIf(); - case 7 -> genWhile(); - case 8 -> genAssignment(); - default -> genExit(); - }; - depth++; - } - indentation -= INDENTATION; - 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; - } - depth--; - int oldCSS = currScopeStart; - currScopeStart = variables.size(); - sb.append("{\n"); - indentation += INDENTATION; - var stop = genStatements(); - indentation -= INDENTATION; - printIndentation().append("}"); - while (variables.size() > currScopeStart) - variables.removeLast(); - currScopeStart = oldCSS; - depth++; - 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(); - 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 while loop. - * @return 0 - */ - public int genWhile() { - sb.append("while("); - genExpression(); - sb.append(") "); - loopDepth++; - genStatementBlock(); - loopDepth--; - return 0; - } - - /** - * Generate a declaration statement. - * @return 0 - */ - public int genDecl() { - String name = getVarName(); - sb.append("int ").append(name).append("="); - genExpression(); - sb.append(";"); - variables.add(name); - return 0; - } - - /** - * Generate an assignment statement. - * @return 0 - */ - public int genAssignment() { - if (variables.isEmpty()) return genDecl(); - genVariable(); - sb.append("="); - genExpression(); - sb.append(";"); - return 0; - } - - /** - * Generate a return statement. - * @return FLAG_STOP - */ - public int genReturn() { - sb.append("return "); - genExpression(); - sb.append(";"); - return FLAG_STOP; - } - - - /** - * Generate a binary expression. - * This method does not care about operator precedence. - */ - public void genExpression() { - var num = randLog(10); - while(num-->0) { - genUnary(); - sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); - } - genUnary(); - } - - /** - * Generate a unary expression. - */ - public void genUnary() { - var num = randLog(6); - while(num-->0) { - sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); - } - genPrimary(); - } - - /** - * Generate a primary expression. - */ - public void genPrimary() { - switch (random.nextInt(10)) { - case 0: - sb.append("true"); - break; - case 1: - sb.append("false"); - break; - case 2: - if (exprDepth == 0) { - // Ensure termination of the generator. - if (random.nextBoolean()) { - genNumber(); - } else { - genVariable(); - } - } else { - sb.append("("); - exprDepth--; - genExpression(); - exprDepth++; - sb.append(")"); - } - break; - case 3, 4, 5, 6: - genNumber(); - break; - default: - genVariable(); - } - } - - /** - * Generate a number. - */ - public void genNumber() { - sb.append(random.nextInt(100)); - } - - /** - * 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() { - if (!generateValid && random.nextInt(100)>97) { - sb.append(getRandomName()); - maybeInvalid = true; - return; - } - if (variables.isEmpty()) { - genNumber(); - } else { - sb.append(variables.get(random.nextInt(variables.size()))); - } - } - -} +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; + +/** + * 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 { + + /** + * Get the list of keywords from the parser. + */ + @SuppressWarnings("unchecked") + private static HashSet getKeywords() { + try { + var field = Parser.class.getDeclaredField("KEYWORDS"); + field.setAccessible(true); + return (HashSet) field.get(new Parser("")); + } catch (Throwable e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * 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 = getKeywords(); + + /** + * 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 = {"-"}; + + + /** + * 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 = 7; + /** + * The depth of expressions allowed. + */ + private int exprDepth = 3; + /** + * Current variables in scope. + */ + private final ArrayList variables = new ArrayList<>(); + /** + * If this needs to generate valid scripts or is also allowed to generate invalid ones. + */ + private final 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< 0 && random.nextInt(10) > 7) { + // Use a variable outside the current scope. + var v = variables.get(random.nextInt(currScopeStart)); + if (variables.lastIndexOf(v) < currScopeStart) return v; + if (!generateValid) { + maybeInvalid = true; + return v; + } + } + // Generate a new random variable name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + + + /** + * 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() { + this.maybeInvalid = false; + variables.add(ScopeNode.ARG0); + currScopeStart = variables.size(); + if ((genStatements() & FLAG_STOP) == 0) { + if (generateValid || random.nextInt(10)<7) { + genReturn(); + } else { + this.maybeInvalid = true; + } + } + 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(10); + for (int i=0; i genBlock(); + case 2 -> genIf(); + case 3 -> genWhile(); + case 4, 5 -> genDecl(); + case 6, 7, 8 -> genAssignment(); + default -> genExit(); + }; + } + + /** + * Generate a statement for if, else and while blocks. + * This is special since it disallows declarations and prefers to generate blocks. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genStatementBlock() { + indentation += INDENTATION; + int res; + if (depth == 0) { + res = random.nextBoolean() ? genAssignment() : genExit(); + } else { + depth--; + res = switch (random.nextInt(10)) { + case 1, 2, 3, 4, 5 -> { + depth++; + indentation -= INDENTATION; + var ret = genBlock(); + indentation += INDENTATION; + depth--; + yield ret; + } + case 6 -> genIf(); + case 7 -> genWhile(); + case 8 -> genAssignment(); + default -> genExit(); + }; + depth++; + } + indentation -= INDENTATION; + 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; + } + depth--; + int oldCSS = currScopeStart; + currScopeStart = variables.size(); + sb.append("{\n"); + indentation += INDENTATION; + var stop = genStatements(); + indentation -= INDENTATION; + printIndentation().append("}"); + while (variables.size() > currScopeStart) + variables.removeLast(); + currScopeStart = oldCSS; + depth++; + 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(); + 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 while loop. + * @return 0 + */ + public int genWhile() { + sb.append("while("); + genExpression(); + sb.append(") "); + loopDepth++; + genStatementBlock(); + loopDepth--; + return 0; + } + + /** + * Generate a declaration statement. + * @return 0 + */ + public int genDecl() { + String name = getVarName(); + sb.append("int ").append(name).append("="); + genExpression(); + sb.append(";"); + variables.add(name); + return 0; + } + + /** + * Generate an assignment statement. + * @return 0 + */ + public int genAssignment() { + if (variables.isEmpty()) return genDecl(); + genVariable(); + sb.append("="); + genExpression(); + sb.append(";"); + return 0; + } + + /** + * Generate a return statement. + * @return FLAG_STOP + */ + public int genReturn() { + sb.append("return "); + genExpression(); + sb.append(";"); + return FLAG_STOP; + } + + + /** + * Generate a binary expression. + * This method does not care about operator precedence. + */ + public void genExpression() { + var num = randLog(10); + while(num-->0) { + genUnary(); + sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); + } + genUnary(); + } + + /** + * Generate a unary expression. + */ + public void genUnary() { + var num = randLog(6); + while(num-->0) { + sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); + } + genPrimary(); + } + + /** + * Generate a primary expression. + */ + public void genPrimary() { + switch (random.nextInt(10)) { + case 0: + sb.append("true"); + break; + case 1: + sb.append("false"); + break; + case 2: + if (exprDepth == 0) { + // Ensure termination of the generator. + if (random.nextBoolean()) { + genNumber(); + } else { + genVariable(); + } + } else { + sb.append("("); + exprDepth--; + genExpression(); + exprDepth++; + sb.append(")"); + } + break; + case 3, 4, 5, 6: + genNumber(); + break; + default: + genVariable(); + } + } + + /** + * Generate a number. + */ + public void genNumber() { + sb.append(random.nextInt(100)); + } + + /** + * 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() { + if (!generateValid && random.nextInt(100)>97) { + sb.append(getRandomName()); + maybeInvalid = true; + return; + } + if (variables.isEmpty()) { + genNumber(); + } else { + sb.append(variables.get(random.nextInt(variables.size()))); + } + } + +} diff --git a/chapter09/README.md b/chapter09/README.md index b54a6c67..c6846bed 100644 --- a/chapter09/README.md +++ b/chapter09/README.md @@ -69,7 +69,7 @@ already had its `_type` lifted but not the other. Depending on which of the two Nodes is kept, we might choose the Node with the "lower" `_type`. This is "lower" in the Type lattice sense. Both types must be correct, and a `compute()` call on both nodes must produce the same Type, which must be equal -to the JOIN. A fix is to compute a JOIN (not a MEET) over the two Nodes' types. +to the JOIN. A fix is to compute a JOIN (not a MEET) over the two Nodes' types. ## Post-Parse Iterative Peepholes @@ -153,7 +153,7 @@ Changes: * Some peepholes (see above) add dependencies if they fail a remote check, by calling `distant.addDep(this)`. The `addDep` call creates `_deps` and filters for various kinds of duplicate adds. -* The `IterPeeps` loop, when it finds a change, also moves all the +* The `IterPeeps` loop, when it finds a change, also moves all the dependents onto the worklist. @@ -162,7 +162,7 @@ Changes: There are more issues we will want to deal with in a later Chapter: * Blindly running peepholes in any order has some drawbacks: - + - Dead and dying stuff might get peepholes done... and then die. Wasted work. - Dead infinite loops often lead to infinite peephole cycles... if only we would get around to working on the "base" of the dead loop it would fold @@ -171,18 +171,18 @@ There are more issues we will want to deal with in a later Chapter: size the same but reduce other things (e.g. swapping a Mul-by-2 with a Shift), and we might end up with a few which try to grow the graph briefly before collapsing. - + * All this means is there's some benefit to running peepholes that reduce the graph directly (e.g. Dead Code Eliminate), before running peeps that reduce other things, before running all other peeps. This implies a sorted worklist, but the count of unique orders is really limited - a radix sort is all that is needed. We'll have to break up the peepholes into some - categories like - - "strictly reducing" vs - - "same Nodes but swapping e.g. `Mul` for `Shift`, vs - - getting more freedom (edge bypass), vs + categories like + - "strictly reducing" vs + - "same Nodes but swapping e.g. `Mul` for `Shift`, vs + - getting more freedom (edge bypass), vs - "grow now because shrink later" (inlining lands in this camp). - + * Also there's a benefit to not always grabbing from either end of the list - many peep patterns might go quadratic if approached from one end or another, because they modify something then push it back onto the list where it @@ -194,7 +194,7 @@ There are more issues we will want to deal with in a later Chapter: * Why isn't `IterPeeps` just passing over all of the Nodes once or twice, instead of using a worklist with the `addDeps` mechanism? We could even visit them in a defs-before-uses order (e.g. Reverse Post Order). - + In the absense of loops exactly one such pass will find all local peepholes, and indeed the Parser already does this. However, this will fail to find opportunities at loops and farther remote cases - and to get those peepholes @@ -203,7 +203,7 @@ There are more issues we will want to deal with in a later Chapter: quadratic. The `addDeps` solution avoids this quadratic cost, in exchange for some more - costs in writing peepholes. + costs in writing peepholes. # Examples diff --git a/chapter09/docs/09-grammar.md b/chapter09/docs/09-grammar.md index c603f335..4c61bfd7 100644 --- a/chapter09/docs/09-grammar.md +++ b/chapter09/docs/09-grammar.md @@ -10,7 +10,7 @@ program statement : returnStatement | declStatement - | blockStatment + | blockStatment | expressionStatement | ifStatement | whileStatement @@ -26,10 +26,10 @@ whileStatement breakStatement : 'break' ';' ; - + continueStatement : 'continue' ';' - ; + ; ifStatement : 'if' '(' expression ')' statement ('else' statement)? @@ -58,15 +58,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -88,11 +88,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter09/docs/09-graph1.gv b/chapter09/docs/09-graph1.gv index 4f712c2d..e6d1851a 100644 --- a/chapter09/docs/09-graph1.gv +++ b/chapter09/docs/09-graph1.gv @@ -64,4 +64,4 @@ return x; Add21 -> Con_20[taillabel=2]; Return24 -> If11:p1[taillabel=0 color=red]; Return24 -> Add21[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter09/docs/09-graph2.gv b/chapter09/docs/09-graph2.gv index 9f921cc0..1a1f7a29 100644 --- a/chapter09/docs/09-graph2.gv +++ b/chapter09/docs/09-graph2.gv @@ -60,4 +60,4 @@ return x; Add20 -> Con_21[taillabel=2]; Return24 -> If11:p1[taillabel=0 color=red]; Return24 -> Add20[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter09/docs/09-graph3.gv b/chapter09/docs/09-graph3.gv index 9df55c1c..37bb1c53 100644 --- a/chapter09/docs/09-graph3.gv +++ b/chapter09/docs/09-graph3.gv @@ -34,4 +34,4 @@ return arg*arg-arg*arg; Sub8 -> Mul7[taillabel=2]; Return9 -> Start2:p0[taillabel=0 color=red]; Return9 -> Sub8[taillabel=1]; -} \ No newline at end of file +} diff --git a/chapter09/docs/09-graph6.gv b/chapter09/docs/09-graph6.gv index 492c320e..56dc6ada 100644 --- a/chapter09/docs/09-graph6.gv +++ b/chapter09/docs/09-graph6.gv @@ -57,4 +57,4 @@ return arg; Return21 -> Phi_arg9[taillabel=1]; Add25 -> Phi_arg9[taillabel=1]; Add25 -> Con_24[taillabel=2]; -} \ No newline at end of file +} diff --git a/chapter09/pom.xml b/chapter09/pom.xml index 17df1310..d9307fb8 100644 --- a/chapter09/pom.xml +++ b/chapter09/pom.xml @@ -11,4 +11,4 @@ chapter09 jar Chapter 9 - \ No newline at end of file + diff --git a/chapter09/src/main/java/com/seaofnodes/simple/GraphEvaluator.java b/chapter09/src/main/java/com/seaofnodes/simple/GraphEvaluator.java index c4b349bd..9fc51c8e 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/GraphEvaluator.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/GraphEvaluator.java @@ -1,181 +1,181 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeInteger; - -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashMap; - -public class GraphEvaluator { - - public enum ResultType { VALUE, FALLTHROUGH, TIMEOUT } - - public record Result(ResultType type, long value) {} - - /** - * 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 (node instanceof StartNode start) return start; - if( visit.get(node._nid) ) return null; - visit.set(node._nid); - for( Node def : node._inputs ) { - var res = findStart(visit,def); - if( res != null ) return res; - } - for( Node use : node._outputs ) { - var res = findStart(visit,use); - if( res != null ) return res; - } - return null; - } - - /** - * Find the control output from a control node - */ - private static Node findControl(Node control) { - for( Node use : control._outputs ) { - if (use.isCFG()) return use; - } - return null; - } - - /** - * Find the projection for a node - */ - private static ProjNode findProjection(Node node, int idx) { - for( Node use : node._outputs ) { - if (use instanceof ProjNode proj && proj._idx == idx) return proj; - } - return null; - } - - /** - * Cache values for phi and parameter projection nodes. - */ - private final HashMap cacheValues = new HashMap<>(); - /** - * Cache for loop phis as they can depend on itself or other loop phis - */ - private long[] loopPhiCache = new long[16]; - - private GraphEvaluator() { - } - - private long div(DivNode div) { - long in2 = getValue(div.in(2)); - return in2 == 0 ? 0 : getValue(div.in(1)) / in2; - } - - /** - * Calculate the value of a node - */ - private long getValue(Node node) { - var cache = cacheValues.get(node); - if (cache != null) return cache; - return switch (node) { - case ConstantNode cons -> ((TypeInteger)cons.compute()).value(); - case AddNode add -> getValue(add.in(1)) + getValue(add.in(2)); - case BoolNode.EQ eq -> getValue(eq.in(1)) == getValue(eq.in(2)) ? 1 : 0; - case BoolNode.LE le -> getValue(le.in(1)) <= getValue(le.in(2)) ? 1 : 0; - case BoolNode.LT lt -> getValue(lt.in(1)) < getValue(lt.in(2)) ? 1 : 0; - case DivNode div -> div(div); - case MinusNode minus -> -getValue(minus.in(1)); - case MulNode mul -> getValue(mul.in(1)) * getValue(mul.in(2)); - case NotNode not -> getValue(not.in(1)) == 0 ? 1 : 0; - case SubNode sub -> getValue(sub.in(1)) - getValue(sub.in(2)); - default -> throw Utils.TODO("Unexpected node " + node); - }; - } - - /** - * Special case of latchPhis when phis can depend on phis of the same region. - */ - private void latchLoopPhis(RegionNode region, Node prev) { - int idx = Utils.find(region._inputs, prev); - assert idx > 0; - int i = 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) { - var value = getValue(phi.in(idx)); - if (i == loopPhiCache.length) loopPhiCache = Arrays.copyOf(loopPhiCache, loopPhiCache.length*2); - loopPhiCache[i++] = value; - } - } - i = 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) cacheValues.put(phi, loopPhiCache[i++]); - } - } - - /** - * Calculate the values of phis of the region and caches the values. The phis are not allowed to depend on other phis of the region. - */ - private void latchPhis(RegionNode region, Node prev) { - int idx = Utils.find(region._inputs, prev); - assert idx > 0; - for( Node use : region._outputs ) { - if (use instanceof PhiNode phi) { - var value = getValue(phi.in(idx)); - cacheValues.put(phi, value); - } - } - } - - /** - * Run the graph until either a return is found or the number of loop iterations are done. - */ - private Result evaluate(StartNode start, long parameter, int loops) { - var parameter1 = findProjection(start, 1); - if (parameter1 != null) cacheValues.put(parameter1, parameter); - Node control = findProjection(start, 0); - Node prev = start; - while (control != null) { - Node next; - switch (control) { - case RegionNode region -> { - if (region instanceof LoopNode && region.in(1) != prev) { - if (loops--<=0) return new Result(ResultType.TIMEOUT, 0); - latchLoopPhis(region, prev); - } else { - latchPhis(region, prev); - } - next = findControl(region); - } - case IfNode cond -> next = findProjection(cond, getValue(cond.in(1)) != 0 ? 0 : 1); - case ReturnNode ret -> { - return new Result(ResultType.VALUE, getValue(ret.in(1))); - } - case ProjNode ignored -> next = findControl(control); - default -> throw Utils.TODO("Unexpected control node " + control); - } - prev = control; - control = next; - } - return new Result(ResultType.FALLTHROUGH, 0); - } - - public static long evaluate(Node graph) { - return evaluate(graph, 0); - } - - public static long evaluate(Node graph, long parameter) { - return evaluate(graph, parameter, 1000); - } - - public static long evaluate(Node graph, long parameter, int loops) { - var res = evaluateWithResult(graph, parameter, loops); - if (res.type== ResultType.TIMEOUT) throw new RuntimeException("Timeout"); - return res.value; - } - - public static Result evaluateWithResult(Node graph, long parameter, int loops) { - var start = findStart(new BitSet(), graph); - if (start == null) return new Result(ResultType.TIMEOUT, 0); - var evaluator = new GraphEvaluator(); - return evaluator.evaluate(start, parameter, loops); - } - -} +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; + +public class GraphEvaluator { + + public enum ResultType { VALUE, FALLTHROUGH, TIMEOUT } + + public record Result(ResultType type, long value) {} + + /** + * 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 (node instanceof StartNode start) return start; + if( visit.get(node._nid) ) return null; + visit.set(node._nid); + for( Node def : node._inputs ) { + var res = findStart(visit,def); + if( res != null ) return res; + } + for( Node use : node._outputs ) { + var res = findStart(visit,use); + if( res != null ) return res; + } + return null; + } + + /** + * Find the control output from a control node + */ + private static Node findControl(Node control) { + for( Node use : control._outputs ) { + if (use.isCFG()) return use; + } + return null; + } + + /** + * Find the projection for a node + */ + private static ProjNode findProjection(Node node, int idx) { + for( Node use : node._outputs ) { + if (use instanceof ProjNode proj && proj._idx == idx) return proj; + } + return null; + } + + /** + * Cache values for phi and parameter projection nodes. + */ + private final HashMap cacheValues = new HashMap<>(); + /** + * Cache for loop phis as they can depend on itself or other loop phis + */ + private long[] loopPhiCache = new long[16]; + + private GraphEvaluator() { + } + + private long div(DivNode div) { + long in2 = getValue(div.in(2)); + return in2 == 0 ? 0 : getValue(div.in(1)) / in2; + } + + /** + * Calculate the value of a node + */ + private long getValue(Node node) { + var cache = cacheValues.get(node); + if (cache != null) return cache; + return switch (node) { + case ConstantNode cons -> ((TypeInteger)cons.compute()).value(); + case AddNode add -> getValue(add.in(1)) + getValue(add.in(2)); + case BoolNode.EQ eq -> getValue(eq.in(1)) == getValue(eq.in(2)) ? 1 : 0; + case BoolNode.LE le -> getValue(le.in(1)) <= getValue(le.in(2)) ? 1 : 0; + case BoolNode.LT lt -> getValue(lt.in(1)) < getValue(lt.in(2)) ? 1 : 0; + case DivNode div -> div(div); + case MinusNode minus -> -getValue(minus.in(1)); + case MulNode mul -> getValue(mul.in(1)) * getValue(mul.in(2)); + case NotNode not -> getValue(not.in(1)) == 0 ? 1 : 0; + case SubNode sub -> getValue(sub.in(1)) - getValue(sub.in(2)); + default -> throw Utils.TODO("Unexpected node " + node); + }; + } + + /** + * Special case of latchPhis when phis can depend on phis of the same region. + */ + private void latchLoopPhis(RegionNode region, Node prev) { + int idx = Utils.find(region._inputs, prev); + assert idx > 0; + int i = 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) { + var value = getValue(phi.in(idx)); + if (i == loopPhiCache.length) loopPhiCache = Arrays.copyOf(loopPhiCache, loopPhiCache.length*2); + loopPhiCache[i++] = value; + } + } + i = 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) cacheValues.put(phi, loopPhiCache[i++]); + } + } + + /** + * Calculate the values of phis of the region and caches the values. The phis are not allowed to depend on other phis of the region. + */ + private void latchPhis(RegionNode region, Node prev) { + int idx = Utils.find(region._inputs, prev); + assert idx > 0; + for( Node use : region._outputs ) { + if (use instanceof PhiNode phi) { + var value = getValue(phi.in(idx)); + cacheValues.put(phi, value); + } + } + } + + /** + * Run the graph until either a return is found or the number of loop iterations are done. + */ + private Result evaluate(StartNode start, long parameter, int loops) { + var parameter1 = findProjection(start, 1); + if (parameter1 != null) cacheValues.put(parameter1, parameter); + Node control = findProjection(start, 0); + Node prev = start; + while (control != null) { + Node next; + switch (control) { + case RegionNode region -> { + if (region instanceof LoopNode && region.in(1) != prev) { + if (loops--<=0) return new Result(ResultType.TIMEOUT, 0); + latchLoopPhis(region, prev); + } else { + latchPhis(region, prev); + } + next = findControl(region); + } + case IfNode cond -> next = findProjection(cond, getValue(cond.in(1)) != 0 ? 0 : 1); + case ReturnNode ret -> { + return new Result(ResultType.VALUE, getValue(ret.in(1))); + } + case ProjNode ignored -> next = findControl(control); + default -> throw Utils.TODO("Unexpected control node " + control); + } + prev = control; + control = next; + } + return new Result(ResultType.FALLTHROUGH, 0); + } + + public static long evaluate(Node graph) { + return evaluate(graph, 0); + } + + public static long evaluate(Node graph, long parameter) { + return evaluate(graph, parameter, 1000); + } + + public static long evaluate(Node graph, long parameter, int loops) { + var res = evaluateWithResult(graph, parameter, loops); + if (res.type== ResultType.TIMEOUT) throw new RuntimeException("Timeout"); + return res.value; + } + + public static Result evaluateWithResult(Node graph, long parameter, int loops) { + var start = findStart(new BitSet(), graph); + if (start == null) return new Result(ResultType.TIMEOUT, 0); + var evaluator = new GraphEvaluator(); + return evaluator.evaluate(start, parameter, loops); + } + +} diff --git a/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 657593d2..b4d64d4c 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -31,7 +31,7 @@ public class GraphVisualizer { sb.append("/*\n"); sb.append(stop._src); sb.append("\n*/\n"); - + // To keep the Scopes below the graph and pointing up into the graph we // need to group the Nodes in a subgraph cluster, and the scopes into a // different subgraph cluster. THEN we can draw edges between the @@ -51,7 +51,7 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges nodes(sb, all); - + // Now the scopes, in a cluster no edges if( xScopes != null ) for( ScopeNode xscope : xScopes ) @@ -66,7 +66,7 @@ public class GraphVisualizer { for( ScopeNode xscope : xScopes ) if( !xscope.isDead() ) scopeEdges( sb, xscope ); - + sb.append("}\n"); return sb.toString(); } @@ -200,13 +200,13 @@ public class GraphVisualizer { sb.append(" color=red"); // Backedges do not add a ranking constraint if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) - sb.append(" constraint=false"); + sb.append(" constraint=false"); sb.append("];\n"); } } } } - + // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scope ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); @@ -233,7 +233,7 @@ public class GraphVisualizer { level++; } } - + /** * Finds all nodes in the graph. */ diff --git a/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java index 8103103c..6dafd95f 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -99,7 +99,7 @@ public abstract class IterPeeps { // 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 diff --git a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java index 5e7da8f8..b3aeeb22 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/Parser.java @@ -13,7 +13,7 @@ import java.util.*; * This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -57,7 +57,7 @@ public class Parser { 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 @@ -83,12 +83,12 @@ public class Parser { @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); } @@ -198,7 +198,7 @@ public class Parser { // 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 @@ -281,7 +281,7 @@ public class Parser { ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection parseStatement(); // Parse true-side ScopeNode tScope = _scope; - + // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection @@ -292,7 +292,7 @@ public class Parser { 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 @@ -604,7 +604,7 @@ public class Parser { skipWhiteSpace(); return peek()==ch; } - + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -631,7 +631,7 @@ public class Parser { return TypeInteger.constant(Long.parseLong(snum)); } - // First letter of an identifier + // First letter of an identifier private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } @@ -647,7 +647,7 @@ public class Parser { return new String(_input, start, --_position - start); } - // + // private boolean isPunctuation(char ch) { return "=;[]<>()+-/*".indexOf(ch) != -1; } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/Utils.java b/chapter09/src/main/java/com/seaofnodes/simple/Utils.java index 4d663c25..45f15327 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/Utils.java @@ -36,4 +36,4 @@ public class Utils { return i; return -1; } -} \ No newline at end of file +} diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java index 5ffb821e..4e30bb55 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -8,7 +8,7 @@ public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Add"; } - + @Override public String glabel() { return "+"; } @Override @@ -17,7 +17,7 @@ public class AddNode extends Node { in(2)._print0(sb.append("+"), visited); return sb.append(")"); } - + @Override public Type compute() { @@ -39,13 +39,13 @@ public class AddNode extends Node { // 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(); @@ -53,7 +53,7 @@ public class AddNode extends Node { // 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) ? @@ -73,7 +73,7 @@ public class AddNode extends Node { // 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 @@ -91,12 +91,12 @@ public class AddNode extends Node { 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; } @@ -114,11 +114,11 @@ public class AddNode extends Node { 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; @@ -147,7 +147,7 @@ public class AddNode extends Node { static PhiNode pcon(Node op, Node dep) { return op instanceof PhiNode phi && phi.allCons(dep) ? phi : null; } - + // Compare two off-spine nodes and decide what order they should be in. // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? // Generally constants always go right, then Phi-of-constants, then muls, then others. @@ -165,10 +165,10 @@ public class AddNode extends Node { if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; - + // Same category of "others" return lo._nid > hi._nid; } - + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java index b4691b27..d3fcec95 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -12,7 +12,7 @@ abstract public class BoolNode extends Node { } abstract String op(); // String opcode name - + @Override public String label() { return getClass().getSimpleName(); } @@ -40,7 +40,7 @@ abstract public class BoolNode extends Node { @Override public Node idealize() { - // Compare of same + // Compare of same if( in(1)==in(2) ) return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index c4ea08b5..9e45198c 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -23,10 +23,10 @@ public class ConstantNode extends Node { super(Parser.START); _con = type; } - + @Override public String label() { return "#"+_con; } - + @Override public String uniqueName() { return "Con_" + _nid; } @@ -34,9 +34,9 @@ public class ConstantNode extends Node { StringBuilder _print1(StringBuilder sb, BitSet visited) { return _con._print(sb); } - + @Override public boolean isMultiTail() { return true; } - + @Override public Type compute() { return _con; } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java index ed1a233c..ba06d037 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -18,7 +18,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -33,6 +33,6 @@ public class DivNode extends Node { @Override public Node idealize() { return null; } - + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/IfNode.java index 52486772..460dce51 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -42,10 +42,10 @@ public class IfNode extends MultiNode { // 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 diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 0b84f897..c192bc20 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -25,5 +25,5 @@ public class LoopNode extends RegionNode { return inProgress() ? null : super.idealize(); } - @Override Node idom() { return entry(); } + @Override Node idom() { return entry(); } } diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 524f63a0..dd092c9e 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -9,15 +9,15 @@ public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } @Override public String label() { return "Minus"; } - + @Override public String glabel() { return "-"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java index d63d2cb1..7e9f88a2 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -9,7 +9,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -35,12 +35,12 @@ public class MulNode extends Node { 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(); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/MultiNode.java index eced9a14..2743bd19 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } // Find a projection by index diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java index 7c82ffd5..5eb9c076 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/Node.java @@ -49,7 +49,7 @@ public abstract class Node { */ public Type _type; - + /** * Immediate dominator tree depth, used to approximate a real IDOM during * parsing where we do not have the whole program, and also peepholes @@ -58,7 +58,7 @@ public abstract class Node { * See {@link ...} */ int _idepth; - + /** * 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. @@ -88,9 +88,9 @@ public abstract class Node { // ------------------------------------------------------------------------ - + // Debugger Printing. - + // {@code toString} is what you get in the debugger. It has to print 1 // line (because this is what a debugger typically displays by default) and // has to be robust with broken graph/nodes. @@ -104,7 +104,7 @@ public abstract class Node { public final String print() { return _print0(new StringBuilder(), new BitSet()).toString(); } - + // This is the common print: check for repeats, check for DEAD and print // "DEAD" else call the per-Node print1. final StringBuilder _print0(StringBuilder sb, BitSet visited) { @@ -144,13 +144,13 @@ public abstract class Node { } public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } - + public boolean isMultiHead() { return false; } public boolean isMultiTail() { return false; } - + // ------------------------------------------------------------------------ // Graph Node & Edge manipulation - + /** * Gets the ith input node * @param i Offset of the input node @@ -166,19 +166,19 @@ public abstract class Node { public boolean isUnused() { return nOuts() == 0; } public boolean isCFG() { 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 @@ -200,7 +200,7 @@ public abstract class Node { // 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 @@ -255,7 +255,7 @@ public abstract class Node { 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 @@ -298,10 +298,10 @@ public abstract class Node { } kill(); } - + // ------------------------------------------------------------------------ // Graph-based optimizations - + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. @@ -390,7 +390,7 @@ public abstract class Node { } return m; } - + /** * This function needs to be * Monotonic @@ -421,7 +421,7 @@ public abstract class Node { 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 @@ -500,8 +500,8 @@ public abstract class Node { 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<>(); @@ -539,7 +539,7 @@ public abstract class Node { _hash=0; // Out of table now } - + // Hash of opcode and inputs @Override public final int hashCode() { if( _hash != 0 ) return _hash; @@ -552,11 +552,11 @@ public abstract class Node { } // 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 @@ -619,7 +619,7 @@ public abstract class Node { WVISIT.clear(); return rez; } - + private E _walk( Function pred ) { if( WVISIT.get(_nid) ) return null; // Been there, done that WVISIT.set(_nid); @@ -629,7 +629,7 @@ public abstract class Node { for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; return null; } - + /** * Debugging utility to find a Node by index */ diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java index ab0f4d01..7a520fec 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -9,15 +9,15 @@ public class NotNode extends Node { public NotNode(Node in) { super(null, in); } @Override public String label() { return "Not"; } - + @Override public String glabel() { return "!"; } - + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { in(1)._print0(sb.append("(!"), visited); return sb.append(")"); } - + @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 2d03b655..ec4c6ad3 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -7,7 +7,7 @@ import java.util.BitSet; public class PhiNode extends Node { final String _label; - + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } @Override public String label() { return "Phi_"+_label; } @@ -59,7 +59,7 @@ public class PhiNode extends Node { return live; // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. + // Phi, but Phis do not make code. // Phi(op(A,B),op(Q,R),op(X,Y)) becomes // op(Phi(A,Q,X), Phi(B,R,Y)). Node op = in(1); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 115bc1eb..25f595a5 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -12,7 +12,7 @@ public class ProjNode extends Node { // Debugging label public final String _label; - + public ProjNode(MultiNode ctrl, int idx, String label) { super(ctrl); _idx = idx; diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 9468c395..c949c2b2 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -11,7 +11,7 @@ import java.util.BitSet; public class RegionNode extends Node { public RegionNode(Node... nodes) { super(nodes); } - + @Override public String label() { return "Region"; } @@ -78,7 +78,7 @@ public class RegionNode extends Node { return true; return false; } - + // Immediate dominator of Region is a little more complicated. private Node _idom; // Immediate dominator cache @Override Node idom() { diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index c10d82d8..922a3649 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -21,7 +21,7 @@ public class ReturnNode extends Node { public Node ctrl() { return in(0); } public Node expr() { return in(1); } - + @Override public String label() { return "Return"; } @@ -31,7 +31,7 @@ public class ReturnNode extends Node { } @Override public boolean isCFG() { return true; } - + @Override public Type compute() { return TypeTuple.make(ctrl()._type,expr()._type); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 6f72dea2..3abea54d 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -29,8 +29,8 @@ public class ScopeNode extends Node { _scopes = new Stack<>(); _type = Type.BOTTOM; } - - + + @Override public String label() { return "Scope"; } @Override @@ -64,7 +64,7 @@ public class ScopeNode extends Node { names[syms.get(name)] = name; return names; } - + @Override public Type compute() { return Type.BOTTOM; } @Override public Node idealize() { return null; } @@ -191,7 +191,7 @@ public class ScopeNode extends Node { that.kill(); // Kill merged scope return r.unkeep().peephole(); } - + // Merge the backedge scope into this loop head scope // We set the second input to the phi from the back edge (i.e. loop body) public void endLoop(ScopeNode back, ScopeNode exit ) { diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5693f27b..e399138b 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -15,7 +15,7 @@ import java.util.BitSet; public class StartNode extends MultiNode { final TypeTuple _args; - + public StartNode(Type[] args) { super(); _args = TypeTuple.make(args); diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java index dccc90a9..602feffc 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -8,7 +8,7 @@ import java.util.BitSet; public class StopNode extends Node { public final String _src; - + public StopNode(String src) { super(); _src = src; @@ -35,7 +35,7 @@ public class StopNode extends Node { public ReturnNode ret() { return nIns()==1 ? (ReturnNode)in(0) : null; } - + @Override public Type compute() { return Type.BOTTOM; diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java index f81ae6d0..ce83122c 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -18,7 +18,7 @@ public class SubNode extends Node { in(2)._print0(sb.append("-"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && diff --git a/chapter09/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter09/src/main/java/com/seaofnodes/simple/type/Type.java index ee6fb741..d0265e83 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/type/Type.java @@ -7,7 +7,7 @@ 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 @@ -18,7 +18,7 @@ import java.util.HashMap; * 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<>(); @@ -59,11 +59,11 @@ public class Type { // Factory method which interns "this" protected T intern() { T nnn = (T)INTERN.get(this); - if( nnn==null ) + 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() { @@ -74,7 +74,7 @@ public class Type { } // Override in subclasses int hash() { return _type; } - + @Override public final boolean equals( Object o ) { if( o==this ) return true; @@ -84,8 +84,8 @@ public class Type { } // 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 @@ -114,7 +114,7 @@ public class Type { // True if this "isa" t; e.g. 17 isa TypeInteger.BOT public boolean isa( Type t ) { return meet(t)==t; } - + // ---------------------------------------------------------- // Our lattice is defined with a MEET and a DUAL. // JOIN is dual of meet of both duals. @@ -132,7 +132,7 @@ public class Type { default -> throw Utils.TODO(); // Should not reach here }; } - + // ---------------------------------------------------------- @Override public final String toString() { diff --git a/chapter09/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter09/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index df41a526..ba045603 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -28,7 +28,7 @@ public class TypeInteger extends Type { public static TypeInteger constant(long con) { return make(true, con); } - @Override + @Override public StringBuilder _print(StringBuilder sb) { if( this==TOP ) return sb.append("IntTop"); if( this==BOT ) return sb.append("IntBot"); @@ -63,7 +63,7 @@ public class TypeInteger extends Type { if( isConstant() ) return this; // Constants are a self-dual return _con==0 ? BOT : TOP; } - + @Override int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); } @Override diff --git a/chapter09/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter09/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 863c5b7d..e82ea00b 100644 --- a/chapter09/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter09/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } @@ -23,7 +23,7 @@ public class TypeTuple extends Type { ts[i] = _types[i].dual(); return make(ts); } - + @Override public StringBuilder _print(StringBuilder sb) { sb.append("["); @@ -45,7 +45,7 @@ public class TypeTuple extends Type { for( Type type : _types ) sum ^= type.hashCode(); return sum; } - + @Override boolean eq( Type t ) { TypeTuple tt = (TypeTuple)t; // Contract diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter01Test.java index 48cc716f..5abfc4cf 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -16,7 +16,7 @@ public class Chapter01Test { 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 ) { @@ -40,7 +40,7 @@ public class Chapter01Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 5efd33e7..3dd22e39 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter02Test { - + @Test public void testParseGrammar() { Parser parser = new Parser("return 1+2*3+-5;"); @@ -64,10 +64,10 @@ public class Chapter02Test { GraphVisualizer gv = new GraphVisualizer(); System.out.println(gv.generateDotOutput(parser)); } - + @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -82,7 +82,7 @@ public class Chapter02Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 8a11107d..6bcfcc4b 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter03Test { - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -49,7 +49,7 @@ public class Chapter03Test { @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter04Test.java index ffbbe9a8..5a024e77 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter04Test { - + @Test public void testPeephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -37,7 +37,7 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testPeephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -51,7 +51,7 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testVarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -112,7 +112,7 @@ public class Chapter04Test { @Test public void testBug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -126,5 +126,5 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + } diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 6bdc0ef6..24ae80b8 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter05Test { - + @Test public void testIfStmt() { Parser parser = new Parser( @@ -26,7 +26,7 @@ return a;"""); StopNode ret = parser.parse().iterate(true); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } - + @Test public void testTest() { Parser parser = new Parser( @@ -41,20 +41,20 @@ return c;""", TypeInteger.BOT); StopNode ret = parser.parse().iterate(true); assertEquals("return Phi(Region16,4,3);", ret.toString()); } - + @Test public void testReturn2() { Parser parser = new Parser( """ -if( arg==1 ) - return 3; -else - return 4; +if( arg==1 ) + return 3; +else + return 4; #showGraph;""", TypeInteger.BOT); StopNode stop = parser.parse(); assertEquals("Stop[ return 3; return 4; ]", stop.toString()); } - + @Test public void testIfMergeB() { Parser parser = new Parser( @@ -140,20 +140,20 @@ return a;"""); StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } - + @Test public void testHalfDef() { - try { + 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 { + try { new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); fail(); } catch( RuntimeException e ) { @@ -163,38 +163,38 @@ return a;"""); @Test public void testRegress1() { - try { + 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 { + 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 { + 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 { + try { new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); } catch( RuntimeException e ) { @@ -204,7 +204,7 @@ return a;"""); @Test public void testKeyword3() { - try { + try { new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter06Test.java index ba0ea9f6..5fece2e2 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -292,5 +292,5 @@ return a+b+c; StopNode ret = parser.parse().iterate(true); assertEquals("return 5;", ret.toString()); } - + } diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 48876598..d42af6c3 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -10,7 +10,7 @@ import static org.junit.Assert.*; public class Chapter07Test { - + @Test public void testExample() { Parser parser = new Parser( @@ -44,7 +44,7 @@ return a; StopNode stop = parser.parse().iterate(true); assertEquals("return Phi(Region22,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString()); } - + @Test public void testWhileNested() { Parser parser = new Parser( @@ -242,4 +242,3 @@ return a; assertTrue(stop.ret().ctrl() instanceof ProjNode); } } - \ No newline at end of file diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 18371494..05485d8c 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -145,10 +145,10 @@ return arg; StopNode stop = parser.parse().iterate(true); assertEquals("return 0;", stop.toString()); } - + @Test public void testBreakOutsideLoop() { - try { + try { new Parser(""" if(arg <= 10) { break; @@ -173,7 +173,7 @@ return arg; StopNode stop = parser.parse().iterate(true); assertEquals("return arg;", stop.toString()); } - + @Test public void testRegress4() { Parser parser = new Parser(""" @@ -189,9 +189,9 @@ return a; """); StopNode stop = parser.parse().iterate(true); assertEquals("return Phi(Region28,Phi(Loop7,1,(Phi_a+1)),Add);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof RegionNode); + assertTrue(stop.ret().ctrl() instanceof RegionNode); } - + @Test public void testRegress5() { Parser parser = new Parser(""" diff --git a/chapter09/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter09/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 8e198218..834ba3d4 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -149,7 +149,7 @@ while(v1+arg) { if(0) while(0) { int arg=arg; while(0) {} -} +} """); StopNode stop = parser.parse().iterate(true); assertEquals("Stop[ ]", stop.toString()); @@ -170,7 +170,7 @@ if(0) while(0) { assertEquals("return 3;", stop.toString()); } - + @Test public void testFuzz0() { Parser parser = new Parser(""" @@ -209,7 +209,7 @@ return -0+0+0; StopNode stop = parser.parse().iterate(true); assertEquals("return 0;", stop.toString()); } - + @Test public void testFuzz3() { Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); diff --git a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 90817d2b..936fb41e 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -1,170 +1,170 @@ -package com.seaofnodes.simple.fuzzer; - -import com.seaofnodes.simple.GraphEvaluator; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StopNode; - -import java.util.ArrayList; -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 the compiled graph with and without peeps is 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) { - for (var ex : exceptions) { - if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; - } - exceptions.add(e); - System.out.println("========== Stack =========="); - e.printStackTrace(System.out); - 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(); - } - - /** - * Check that two graphs result in the same output when supplied with a value - * @param stop1 Stop node of the first graph - * @param stop2 Stop node of the second graph - * @param in The input value to both graphs to test for an equal output - */ - private static void checkGraphs(StopNode stop1, StopNode stop2, long in) { - var e1 = GraphEvaluator.evaluateWithResult(stop1, in, EVAL_TIMEOUT); - var e2 = GraphEvaluator.evaluateWithResult(stop2, in, EVAL_TIMEOUT); - if (e1.type() == GraphEvaluator.ResultType.TIMEOUT || e2.type() == GraphEvaluator.ResultType.TIMEOUT) return; - if (e1.type() != e2.type()) - throw new RuntimeException("Different calculations types " + e1.type() + " vs " + e2.type()); - if (e1.value() != e2.value()) - throw new RuntimeException("Different calculations values " + e1.value() + " vs " + e2.value()); - } - - /** - * Run checks for script. Compile the script with peeps enabled and disabled. - * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid. - * If the script was successfully parsed check that both version behave the same. - * @param script The script to test - * @param valid If the script is definitely valid. If not some exceptions may be suppressed. - */ - private static void runCheck(String script, boolean valid) { - StopNode stop1; - try { - stop1 = FuzzerUtils.parse(script, false); - } catch (RuntimeException e1) { - try { - FuzzerUtils.parse(script, true); - } catch (RuntimeException e2) { - if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { - if (!valid || e1.getClass() == RuntimeException.class) return; - } else { - e1.addSuppressed(e2); - } - } - throw e1; - } - var stop2 = FuzzerUtils.parse(script, true); - checkGraphs(stop1, stop2, 0); - checkGraphs(stop1, stop2, 1); - checkGraphs(stop1, stop2, 10); - } - - /** - * Check that the script does not result in exceptions. - * @param script The script to test. - * @param valid If the script is valid or if it might contain syntax errors. - */ - private void check(String script, boolean valid) { - try { - runCheck(script, valid); - } catch (Throwable e) { - recordException(e, script, s->runCheck(s, valid)); - } - } - - /** - * 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, false).genProgram(); - check(sb.toString(), valid); - } - - - /** - * Check that no exceptions happened. - * @return true if no exception happened - */ - public boolean noExceptions() { - return exceptions.isEmpty(); - } -} +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.GraphEvaluator; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +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 the compiled graph with and without peeps is 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) { + for (var ex : exceptions) { + if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; + } + exceptions.add(e); + System.out.println("========== Stack =========="); + e.printStackTrace(System.out); + 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(); + } + + /** + * Check that two graphs result in the same output when supplied with a value + * @param stop1 Stop node of the first graph + * @param stop2 Stop node of the second graph + * @param in The input value to both graphs to test for an equal output + */ + private static void checkGraphs(StopNode stop1, StopNode stop2, long in) { + var e1 = GraphEvaluator.evaluateWithResult(stop1, in, EVAL_TIMEOUT); + var e2 = GraphEvaluator.evaluateWithResult(stop2, in, EVAL_TIMEOUT); + if (e1.type() == GraphEvaluator.ResultType.TIMEOUT || e2.type() == GraphEvaluator.ResultType.TIMEOUT) return; + if (e1.type() != e2.type()) + throw new RuntimeException("Different calculations types " + e1.type() + " vs " + e2.type()); + if (e1.value() != e2.value()) + throw new RuntimeException("Different calculations values " + e1.value() + " vs " + e2.value()); + } + + /** + * Run checks for script. Compile the script with peeps enabled and disabled. + * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid. + * If the script was successfully parsed check that both version behave the same. + * @param script The script to test + * @param valid If the script is definitely valid. If not some exceptions may be suppressed. + */ + private static void runCheck(String script, boolean valid) { + StopNode stop1; + try { + stop1 = FuzzerUtils.parse(script, false); + } catch (RuntimeException e1) { + try { + FuzzerUtils.parse(script, true); + } catch (RuntimeException e2) { + if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { + if (!valid || e1.getClass() == RuntimeException.class) return; + } else { + e1.addSuppressed(e2); + } + } + throw e1; + } + var stop2 = FuzzerUtils.parse(script, true); + checkGraphs(stop1, stop2, 0); + checkGraphs(stop1, stop2, 1); + checkGraphs(stop1, stop2, 10); + } + + /** + * Check that the script does not result in exceptions. + * @param script The script to test. + * @param valid If the script is valid or if it might contain syntax errors. + */ + private void check(String script, boolean valid) { + try { + runCheck(script, valid); + } catch (Throwable e) { + recordException(e, script, s->runCheck(s, valid)); + } + } + + /** + * 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, false).genProgram(); + check(sb.toString(), valid); + } + + + /** + * Check that no exceptions happened. + * @return true if no exception happened + */ + public boolean noExceptions() { + return exceptions.isEmpty(); + } +} diff --git a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index a331e5a0..f13a51e1 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -1,138 +1,138 @@ -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 value 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; - - /** - * 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"); - set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT")); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - /** - * Check that eiter one of the objects is null or that both are equal. - */ - private static boolean equalOrNull(Object a, Object b) { - if (a == null || b == null) return true; - 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 (!equalOrNull(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 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; + + /** + * 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"); + set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT")); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Check that eiter one of the objects is null or that both are equal. + */ + private static boolean equalOrNull(Object a, Object b) { + if (a == null || b == null) return true; + 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 (!equalOrNull(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=(])--"), ""), - new Replace(Pattern.compile("\\b--"), "+"), - new Replace(Pattern.compile("\\b\\+-"), "-"), - new Replace(Pattern.compile("\\bfalse\\b"), "0"), - new Replace(Pattern.compile("\\btrue\\b"), "1"), - new Replace(Pattern.compile("-"), ""), - new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), - new Replace(Pattern.compile("\\{(?:[^{}]|\\{})+}"), "{}"), - new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)}"), "$1"), - new Replace(Pattern.compile("\\(([^()]+)\\)"), "$1"), - new Replace(Pattern.compile("\\b\\w+\\b"), "0"), - new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "1"), - new Replace(Pattern.compile("\\d+\\+"), ""), - new Replace(Pattern.compile("\\+\\d+"), ""), - new Replace(Pattern.compile("(?<=\\n|^)[^\\n]*(?=\\n|$)"), "\n"), - new Replace(Pattern.compile("\\n+"), "\n"), - new Replace(Pattern.compile("^\\n+|\\n+$"), ""), - new Replace(Pattern.compile("else *"), ""), - new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), - new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), - }; - - /** - * 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, String with) { - var matcher = pattern.matcher(script); - var sb = new StringBuilder(); - int last = 0; - while (matcher.find()) { - var pos = sb.length(); - matcher.appendReplacement(sb, with); - var tail = sb.length(); - matcher.appendTail(sb); - if (tester.test(sb.toString())) { - sb.setLength(tail); - } else { - sb.setLength(pos); - sb.append(script, last, matcher.end()); - } - last = matcher.end(); - } - matcher.appendTail(sb); - return sb.toString(); - } - - /** - * Clean up variable names from the random ones generated by the fuzzer. - */ - private String cleanVariables(String script) { - var matcher = Pattern.compile("\\bint ([0-9a-zA-Z_]+)").matcher(script); - int num = 0; - while (matcher.find()) { - var var = matcher.group(1); - if (var.matches("v\\d+")) continue; - while (script.contains("v"+num)) num++; - var n = script.replaceAll("\\b"+var+"\\b", "v" + num); - if (tester.test(n)) { - script = n; - } - } - return script; - } - - /** - * Run all rewrite rules once. - */ - private String doAllReplacements(String script) { - for (var replace: REPLACEMENTS) { - script = doReplacement(script, replace.p, replace.r); - } - 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); - }); - } - -} +package com.seaofnodes.simple.fuzzer; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * This class is used to reduce test cases created by the fuzzer. + * It applies rewrite rules to the test case and checks that the + * test still produces the same exception. + * This is done in a fixpoint iteration until every rewrite produces + * no or a different exception. + */ +public class Reducer { + + /** + * Holder for the rewrite rules + */ + private record Replace(Pattern p, String r) {} + + /** + * List of rewrite rules. + */ + private static final Replace[] REPLACEMENTS = { + new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), + new Replace(Pattern.compile("\\b--"), "+"), + new Replace(Pattern.compile("\\b\\+-"), "-"), + new Replace(Pattern.compile("\\bfalse\\b"), "0"), + new Replace(Pattern.compile("\\btrue\\b"), "1"), + new Replace(Pattern.compile("-"), ""), + new Replace(Pattern.compile("(?=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), + new Replace(Pattern.compile("\\{(?:[^{}]|\\{})+}"), "{}"), + new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)}"), "$1"), + new Replace(Pattern.compile("\\(([^()]+)\\)"), "$1"), + new Replace(Pattern.compile("\\b\\w+\\b"), "0"), + new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "1"), + new Replace(Pattern.compile("\\d+\\+"), ""), + new Replace(Pattern.compile("\\+\\d+"), ""), + new Replace(Pattern.compile("(?<=\\n|^)[^\\n]*(?=\\n|$)"), "\n"), + new Replace(Pattern.compile("\\n+"), "\n"), + new Replace(Pattern.compile("^\\n+|\\n+$"), ""), + new Replace(Pattern.compile("else *"), ""), + new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), + }; + + /** + * 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, String with) { + var matcher = pattern.matcher(script); + var sb = new StringBuilder(); + int last = 0; + while (matcher.find()) { + var pos = sb.length(); + matcher.appendReplacement(sb, with); + var tail = sb.length(); + matcher.appendTail(sb); + if (tester.test(sb.toString())) { + sb.setLength(tail); + } else { + sb.setLength(pos); + sb.append(script, last, matcher.end()); + } + last = matcher.end(); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * Clean up variable names from the random ones generated by the fuzzer. + */ + private String cleanVariables(String script) { + var matcher = Pattern.compile("\\bint ([0-9a-zA-Z_]+)").matcher(script); + int num = 0; + while (matcher.find()) { + var var = matcher.group(1); + if (var.matches("v\\d+")) continue; + while (script.contains("v"+num)) num++; + var n = script.replaceAll("\\b"+var+"\\b", "v" + num); + if (tester.test(n)) { + script = n; + } + } + return script; + } + + /** + * Run all rewrite rules once. + */ + private String doAllReplacements(String script) { + for (var replace: REPLACEMENTS) { + script = doReplacement(script, replace.p, replace.r); + } + 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/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index ad0c0a4a..ad98f7e2 100644 --- a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -1,456 +1,456 @@ -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; - -/** - * 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 { - - /** - * 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 = {"-"}; - - - /** - * 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 = 20; - /** - * The depth of expressions allowed. - */ - private int exprDepth = 30; - /** - * Current variables in scope. - */ - private final ArrayList variables = new ArrayList<>(); - /** - * If this needs to generate valid scripts or is also allowed to generate invalid ones. - */ - private final 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< 0 && random.nextInt(10) > 7) { - // Use a variable outside the current scope. - var v = variables.get(random.nextInt(currScopeStart)); - if (variables.lastIndexOf(v) < currScopeStart) return v; - if (!generateValid) { - maybeInvalid = true; - return v; - } - } - // Generate a new random variable name - StringBuilder sb = getRandomName(); - var v = sb.toString(); - if (!generateValid && (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart)) { - maybeInvalid = true; - return v; - } - while (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart) { - sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); - v = sb.toString(); - } - return v; - } - - - - /** - * 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() { - this.maybeInvalid = false; - variables.add(ScopeNode.ARG0); - currScopeStart = variables.size(); - if ((genStatements() & FLAG_STOP) == 0) { - if (generateValid || random.nextInt(10)<7) { - genReturn(); - } else { - this.maybeInvalid = true; - } - } - 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(10); // (30); - for (int i=0; i genBlock(); - case 2 -> genIf(); - case 3 -> genWhile(); - case 4, 5 -> genDecl(); - case 6, 7, 8 -> genAssignment(); - default -> genExit(); - }; - } - - /** - * Generate a statement for if, else and while blocks. - * This is special since it disallows declarations and prefers to generate blocks. - * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement - */ - public int genStatementBlock() { - indentation += INDENTATION; - int res; - if (depth == 0) { - res = random.nextBoolean() ? genAssignment() : genExit(); - } else { - depth--; - res = switch (random.nextInt(10)) { - case 1, 2, 3, 4, 5 -> { - depth++; - indentation -= INDENTATION; - var ret = genBlock(); - indentation += INDENTATION; - depth--; - yield ret; - } - case 6 -> genIf(); - case 7 -> genWhile(); - case 8 -> genAssignment(); - default -> genExit(); - }; - depth++; - } - indentation -= INDENTATION; - 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; - } - depth--; - int oldCSS = currScopeStart; - currScopeStart = variables.size(); - sb.append("{\n"); - indentation += INDENTATION; - var stop = genStatements(); - indentation -= INDENTATION; - printIndentation().append("}"); - while (variables.size() > currScopeStart) - variables.removeLast(); - currScopeStart = oldCSS; - depth++; - 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(); - 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 while loop. - * @return 0 - */ - public int genWhile() { - sb.append("while("); - genExpression(); - sb.append(") "); - loopDepth++; - genStatementBlock(); - loopDepth--; - return 0; - } - - /** - * Generate a declaration statement. - * @return 0 - */ - public int genDecl() { - String name = getVarName(); - sb.append("int ").append(name).append("="); - genExpression(); - sb.append(";"); - variables.add(name); - return 0; - } - - /** - * Generate an assignment statement. - * @return 0 - */ - public int genAssignment() { - if (variables.isEmpty()) return genDecl(); - genVariable(); - sb.append("="); - genExpression(); - sb.append(";"); - return 0; - } - - /** - * Generate a return statement. - * @return FLAG_STOP - */ - public int genReturn() { - sb.append("return "); - genExpression(); - sb.append(";"); - return FLAG_STOP; - } - - - /** - * Generate a binary expression. - * This method does not care about operator precedence. - */ - public void genExpression() { - var num = randLog(10); - while(num-->0) { - genUnary(); - sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); - } - genUnary(); - } - - /** - * Generate a unary expression. - */ - public void genUnary() { - var num = randLog(6); - while(num-->0) { - sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); - } - genPrimary(); - } - - /** - * Generate a primary expression. - */ - public void genPrimary() { - switch (random.nextInt(10)) { - case 0: - sb.append("true"); - break; - case 1: - sb.append("false"); - break; - case 2: - if (exprDepth == 0) { - // Ensure termination of the generator. - if (random.nextBoolean()) { - genNumber(); - } else { - genVariable(); - } - } else { - sb.append("("); - exprDepth--; - genExpression(); - exprDepth++; - sb.append(")"); - } - break; - case 3, 4, 5, 6: - genNumber(); - break; - default: - genVariable(); - } - } - - /** - * Generate a number. - */ - public void genNumber() { - sb.append(random.nextInt(100)); - } - - /** - * 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() { - if (!generateValid && random.nextInt(100)>97) { - sb.append(getRandomName()); - maybeInvalid = true; - return; - } - if (variables.isEmpty()) { - genNumber(); - } else { - sb.append(variables.get(random.nextInt(variables.size()))); - } - } - -} +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; + +/** + * 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 { + + /** + * 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 = {"-"}; + + + /** + * 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 = 20; + /** + * The depth of expressions allowed. + */ + private int exprDepth = 30; + /** + * Current variables in scope. + */ + private final ArrayList variables = new ArrayList<>(); + /** + * If this needs to generate valid scripts or is also allowed to generate invalid ones. + */ + private final 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< 0 && random.nextInt(10) > 7) { + // Use a variable outside the current scope. + var v = variables.get(random.nextInt(currScopeStart)); + if (variables.lastIndexOf(v) < currScopeStart) return v; + if (!generateValid) { + maybeInvalid = true; + return v; + } + } + // Generate a new random variable name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || variables.lastIndexOf(v) >= currScopeStart) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + + + /** + * 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() { + this.maybeInvalid = false; + variables.add(ScopeNode.ARG0); + currScopeStart = variables.size(); + if ((genStatements() & FLAG_STOP) == 0) { + if (generateValid || random.nextInt(10)<7) { + genReturn(); + } else { + this.maybeInvalid = true; + } + } + 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(10); // (30); + for (int i=0; i genBlock(); + case 2 -> genIf(); + case 3 -> genWhile(); + case 4, 5 -> genDecl(); + case 6, 7, 8 -> genAssignment(); + default -> genExit(); + }; + } + + /** + * Generate a statement for if, else and while blocks. + * This is special since it disallows declarations and prefers to generate blocks. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genStatementBlock() { + indentation += INDENTATION; + int res; + if (depth == 0) { + res = random.nextBoolean() ? genAssignment() : genExit(); + } else { + depth--; + res = switch (random.nextInt(10)) { + case 1, 2, 3, 4, 5 -> { + depth++; + indentation -= INDENTATION; + var ret = genBlock(); + indentation += INDENTATION; + depth--; + yield ret; + } + case 6 -> genIf(); + case 7 -> genWhile(); + case 8 -> genAssignment(); + default -> genExit(); + }; + depth++; + } + indentation -= INDENTATION; + 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; + } + depth--; + int oldCSS = currScopeStart; + currScopeStart = variables.size(); + sb.append("{\n"); + indentation += INDENTATION; + var stop = genStatements(); + indentation -= INDENTATION; + printIndentation().append("}"); + while (variables.size() > currScopeStart) + variables.removeLast(); + currScopeStart = oldCSS; + depth++; + 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(); + 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 while loop. + * @return 0 + */ + public int genWhile() { + sb.append("while("); + genExpression(); + sb.append(") "); + loopDepth++; + genStatementBlock(); + loopDepth--; + return 0; + } + + /** + * Generate a declaration statement. + * @return 0 + */ + public int genDecl() { + String name = getVarName(); + sb.append("int ").append(name).append("="); + genExpression(); + sb.append(";"); + variables.add(name); + return 0; + } + + /** + * Generate an assignment statement. + * @return 0 + */ + public int genAssignment() { + if (variables.isEmpty()) return genDecl(); + genVariable(); + sb.append("="); + genExpression(); + sb.append(";"); + return 0; + } + + /** + * Generate a return statement. + * @return FLAG_STOP + */ + public int genReturn() { + sb.append("return "); + genExpression(); + sb.append(";"); + return FLAG_STOP; + } + + + /** + * Generate a binary expression. + * This method does not care about operator precedence. + */ + public void genExpression() { + var num = randLog(10); + while(num-->0) { + genUnary(); + sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); + } + genUnary(); + } + + /** + * Generate a unary expression. + */ + public void genUnary() { + var num = randLog(6); + while(num-->0) { + sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); + } + genPrimary(); + } + + /** + * Generate a primary expression. + */ + public void genPrimary() { + switch (random.nextInt(10)) { + case 0: + sb.append("true"); + break; + case 1: + sb.append("false"); + break; + case 2: + if (exprDepth == 0) { + // Ensure termination of the generator. + if (random.nextBoolean()) { + genNumber(); + } else { + genVariable(); + } + } else { + sb.append("("); + exprDepth--; + genExpression(); + exprDepth++; + sb.append(")"); + } + break; + case 3, 4, 5, 6: + genNumber(); + break; + default: + genVariable(); + } + } + + /** + * Generate a number. + */ + public void genNumber() { + sb.append(random.nextInt(100)); + } + + /** + * 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() { + if (!generateValid && random.nextInt(100)>97) { + sb.append(getRandomName()); + maybeInvalid = true; + return; + } + if (variables.isEmpty()) { + genNumber(); + } else { + sb.append(variables.get(random.nextInt(variables.size()))); + } + } + +} diff --git a/chapter10/README.md b/chapter10/README.md index 1fb69feb..bd46a70e 100644 --- a/chapter10/README.md +++ b/chapter10/README.md @@ -20,15 +20,15 @@ The core ideas regarding `Memory` are: * The program starts and stops with a single `Memory` value that encapsulates the whole of available memory; `Start` produces memory and `Return` consumes - memory. + memory. * Each `struct` and field carves up this memory into "alias classes", a unique class for each combination of struct and field. * Memory in different alias classes *never* aliases; memory in the same alias class *always* aliases. This is sometimes called "equivalence class" - aliasing. - + aliasing. + * Equivalence class aliasing can be directly represented in the Sea of Nodes, as such we will not use any other side-structure for aliasing. This also means with normal peepholes we can do alias-based optimizations such as @@ -45,7 +45,7 @@ the aliased names. Data accesses to the same alias class (struct+field) are serialized as defined by the source program; data in other classes is allowed to be accessed in -parallel. +parallel. Simple, like Java, is a strongly typed language. Therefore, two variables that have incompatible types cannot possibly be names to the same location in @@ -87,7 +87,7 @@ Then we can imagine assigning following alias class IDs So at this point we have sliced memory into 5 distinct alias classes. Note that the `x` and `y` in `Vec2D` do not alias `x` and `y` in `Vec3D`. -In this chapter we do not have inheritance or sub-typing. But if we had subtyping and `Vec3D` +In this chapter we do not have inheritance or sub-typing. But if we had subtyping and `Vec3D` was a subtype of `Vec2D` then `x` and `y` would alias and would be given the same alias class. ## Extensions to Intermediate Representation @@ -99,7 +99,7 @@ We add following new Node types to support memory operations: | New | Mem | Create ptr to new object | Control, Struct type | Ptr value | | Store | Mem | Stores a value in a struct field | Memory slice (aliased by struct+field), Ptr, Field, Value | Memory slice (aliased by struct+field) | | Load | Mem | Loads a value from a field | Memory slice (aliased by struct+field), Ptr, Field | Value loaded | -| Cast | Data | Upcasts a value | Control, Data, type | Upcast value | +| Cast | Data | Upcasts a value | Control, Data, type | Upcast value | * New takes the current control as an input so that it is pinned correctly in @@ -118,7 +118,7 @@ Additionally, the following Node types will be enhanced: ## Enhanced Type Lattice -The Type Lattice for Simple has a major revision in this chapter. +The Type Lattice for Simple has a major revision in this chapter. ![Graph1](./docs/lattice.svg) @@ -137,7 +137,7 @@ Within the Type Lattice, we now have the following type domains: * `?` suffix represents the union of a pointer to some type and `null`. * `null` pointer evaluates to False and non-null pointers evaluate to True, as in C. * Memory (new) - Represents memory and memory slices - * `MEM#TOP` - Nothing known about a slice + * `MEM#TOP` - Nothing known about a slice * `#n` - refers to a memory slice * `MEM#BOT` - all of memory @@ -158,10 +158,10 @@ We make use of following operations on the lattice. * Thus, dual of `Top` is `Bot`. * The dual of `*$TOP` is `*$BOT?`. * For structs, the dual is obtained by computing the dual of each struct member. Thus, dual of `*S1` is not `*S1?`. - + * The `join` operation takes two types and computes the least upper bound type. - Similar to `meet` this can thought of as set intersection (ANDing) of its input types. - Because our lattice structure is simple, we compute `join` with a well known identity: + Similar to `meet` this can thought of as set intersection (ANDing) of its input types. + Because our lattice structure is simple, we compute `join` with a well known identity: `JOIN(x,y) === MEET(x.dual,y.dual).dual` * Example, join of `*S1` and `*S2` results in `*$TOP`. * Join of `1` and `2` results in `Int Top`. @@ -170,7 +170,7 @@ We make use of following operations on the lattice. As we construct the Sea of Nodes graph, we ensure that values stay inside the domain they are created in - that is ptr fields will never contain ints (obviously)... nor even the generic `TOP` and `BOT`. There are a couple of -nuances worth highlighting. +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 @@ -231,22 +231,22 @@ To enable this behaviour, we make 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 is done using a `Cast` op with the type `*void`. This does a lattice JOIN of the original type and `*void`. 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`; occuring 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`. @@ -254,7 +254,7 @@ To enable this behaviour, we make following enhancements. * 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 is a not `null` ptr value, we convert to `0`. See `NotNode.compute()`. - + ## Memory Operations Example This is a simple example illustrating how loads and stores are ordered based on aliasing. @@ -321,4 +321,4 @@ Note that at this stage the `bar` object is dead therefore the allocation of bar [^1]: Diwan A., McKinley K.S., Moss J.E.B. (1998). - Type Based Alias Analysis. \ No newline at end of file + Type Based Alias Analysis. diff --git a/chapter10/docs/10-grammar.md b/chapter10/docs/10-grammar.md index cb68df3d..01863e7f 100644 --- a/chapter10/docs/10-grammar.md +++ b/chapter10/docs/10-grammar.md @@ -11,7 +11,7 @@ statement : returnStatement | structDeclaration | declStatement - | blockStatment + | blockStatment | expressionStatement | ifStatement | whileStatement @@ -26,7 +26,7 @@ field fields : field+ - ; + ; structDeclaration : 'struct' IDENTIFIER '{' fields '}' @@ -39,10 +39,10 @@ whileStatement breakStatement : 'break' ';' ; - + continueStatement : 'continue' ';' - ; + ; ifStatement : 'if' '(' expression ')' statement ('else' statement)? @@ -77,15 +77,15 @@ returnStatement expression : comparisonExpression ; - + comparisonExpression : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* ; - + additiveExpression - : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* ; - + multiplicativeExpression : unaryExpression (('*' | '/') unaryExpression)* ; @@ -96,7 +96,7 @@ unaryExpression ; newExpression - : 'new' IDENTIFIER + : 'new' IDENTIFIER ; fieldExpression @@ -118,11 +118,11 @@ INTEGER_LITERAL : [1-9][0-9]* | [0] ; - + IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; - + NON_DIGIT: [a-zA-Z_]; DEC_DIGIT: [0-9]; -``` \ No newline at end of file +``` diff --git a/chapter10/docs/build-graphs.sh b/chapter10/docs/build-graphs.sh index 8f6afed7..67dd302f 100644 --- a/chapter10/docs/build-graphs.sh +++ b/chapter10/docs/build-graphs.sh @@ -3,4 +3,3 @@ dot -Tsvg example2a.gv > example2a.svg dot -Tsvg example2b.gv > example2b.svg dot -Tsvg example2c.gv > example2c.svg dot -Tsvg lattice.gv > lattice.svg - diff --git a/chapter10/docs/example2b.gv b/chapter10/docs/example2b.gv index a7d4ac62..d732e874 100644 --- a/chapter10/docs/example2b.gv +++ b/chapter10/docs/example2b.gv @@ -53,4 +53,4 @@ return bar.a; Return19 -> Store17[taillabel=2 color=blue]; Return19 -> Store13[taillabel=3 color=blue]; Return19 -> Start2:p4[taillabel=4 color=blue]; -} \ No newline at end of file +} diff --git a/chapter10/docs/example2c.gv b/chapter10/docs/example2c.gv index 7bd21542..76db03bb 100644 --- a/chapter10/docs/example2c.gv +++ b/chapter10/docs/example2c.gv @@ -50,4 +50,4 @@ return bar.a; Store13 -> Start2:p3[taillabel=1 color=blue]; Store13 -> new10[taillabel=2]; Store13 -> Con_11[taillabel=3]; -} \ No newline at end of file +} diff --git a/chapter10/docs/lattice.gv b/chapter10/docs/lattice.gv index bf7403f7..c074f1a4 100644 --- a/chapter10/docs/lattice.gv +++ b/chapter10/docs/lattice.gv @@ -132,4 +132,4 @@ digraph Lattice { {rank=same; PTR_S1OPT; PTR_SOPTDOTS; PTR_SNOPT; } {rank=same; INT_BOT; CTRL; STRUCT_BOT; PTR_BOT; MEM_BOT;} {rank=same; BOT;} -} \ No newline at end of file +} diff --git a/chapter10/pom.xml b/chapter10/pom.xml index fccb1140..33a558e1 100644 --- a/chapter10/pom.xml +++ b/chapter10/pom.xml @@ -11,4 +11,4 @@ chapter10 jar Chapter 10 - \ No newline at end of file + diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java index 9f23593e..4f2af769 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java @@ -62,4 +62,4 @@ public class Utils { assert s1==s1.intern(); return false; } -} \ No newline at end of file +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java index ed1a233c..ba06d037 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -18,7 +18,7 @@ public class DivNode extends Node { in(2)._print0(sb.append("/"), visited); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && @@ -33,6 +33,6 @@ public class DivNode extends Node { @Override public Node idealize() { 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/MulNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java index d63d2cb1..7e9f88a2 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -9,7 +9,7 @@ public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @Override public String label() { return "Mul"; } - + @Override public String glabel() { return "*"; } @Override @@ -35,12 +35,12 @@ public class MulNode extends Node { 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(); diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java index eced9a14..2743bd19 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; public abstract class MultiNode extends Node { - + public MultiNode(Node... inputs) { super(inputs); } // Find a projection by index diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java index 48cc716f..5abfc4cf 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -16,7 +16,7 @@ public class Chapter01Test { 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 ) { @@ -40,7 +40,7 @@ public class Chapter01Test { @Test public void testBad2() { - try { + try { new Parser("return 0123;").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 351cdb16..107225ae 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter02Test { - + @Test public void testParseGrammar() { Parser parser = new Parser("return 1+2*3+-5;"); @@ -64,10 +64,10 @@ public class Chapter02Test { GraphVisualizer gv = new GraphVisualizer(); System.out.println(gv.generateDotOutput(parser)); } - + @Test public void testBad1() { - try { + try { new Parser("ret").parse(); fail(); } catch( RuntimeException e ) { @@ -82,7 +82,7 @@ public class Chapter02Test { @Test public void testBad4() { - try { + try { new Parser("return 100").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 8a11107d..6bcfcc4b 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter03Test { - + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -49,7 +49,7 @@ public class Chapter03Test { @Test public void testSelfAssign() { - try { + try { new Parser("int a=a; return a;").parse(); fail(); } catch( RuntimeException e ) { diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java index ffbbe9a8..5a024e77 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -9,7 +9,7 @@ import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter04Test { - + @Test public void testPeephole() { Parser parser = new Parser("return 1+arg+2; #showGraph;"); @@ -37,7 +37,7 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return (arg*2);", ret.print()); } - + @Test public void testPeephole3() { Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); @@ -51,7 +51,7 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return arg;", ret.print()); } - + @Test public void testVarArg() { Parser parser = new Parser("return arg; #showGraph;"); @@ -112,7 +112,7 @@ public class Chapter04Test { @Test public void testBug3() { - try { + try { new Parser("inta=1; return a;").parse(); fail(); } catch( RuntimeException e ) { @@ -126,5 +126,5 @@ public class Chapter04Test { StopNode ret = parser.parse(); assertEquals("return (-arg);", ret.print()); } - + } diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java index b0b48990..8e651858 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -150,7 +150,7 @@ while(v1+arg) { if(0) while(0) { int arg=arg; while(0) {} -} +} """); StopNode stop = parser.parse().iterate(true); assertEquals("Stop[ ]", stop.toString()); @@ -171,7 +171,7 @@ if(0) while(0) { assertEquals("return 3;", stop.toString()); } - + @Test public void testFuzz0() { Parser parser = new Parser(""" @@ -210,7 +210,7 @@ return -0+0+0; StopNode stop = parser.parse().iterate(true); assertEquals("return 0;", stop.toString()); } - + @Test public void testFuzz3() { Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); diff --git a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 3720dd68..e5961815 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -1,74 +1,74 @@ -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._disablePeephole = true; - StopNode stop = parser.parse(true);//.iterate(true); - 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(false).iterate(true); - 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(false).iterate(true); - var eval = new Evaluator(stop); - assertEquals(2L, eval.evaluate(0, 10)); - assertEquals(2L, eval.evaluate(1, 10)); - } - -} +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._disablePeephole = true; + StopNode stop = parser.parse(true);//.iterate(true); + 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(false).iterate(true); + 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(false).iterate(true); + 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/evaluator/Evaluator.java b/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 8622eb25..3cf5c57b 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -1,209 +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] = parameter; - for(int i=2;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 {"); + 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] = parameter; + for(int i=2;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); - } - -} +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +import java.util.*; + +/** + * A late scheduler for test cases. + *
+ *
+ * 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 index 29a6853c..51e39cd0 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -134,16 +134,16 @@ public class Fuzzer { 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; @@ -174,7 +174,7 @@ public class Fuzzer { check(sb.toString(), valid, seed); } - + /** * Check that no exceptions happened. * @return true if no exception happened -- cgit v1.2.3