summaryrefslogtreecommitdiff
path: root/chapter09
diff options
context:
space:
mode:
Diffstat (limited to 'chapter09')
-rw-r--r--chapter09/README.md22
-rw-r--r--chapter09/docs/09-grammar.md20
-rw-r--r--chapter09/docs/09-graph1.gv2
-rw-r--r--chapter09/docs/09-graph2.gv2
-rw-r--r--chapter09/docs/09-graph3.gv2
-rw-r--r--chapter09/docs/09-graph6.gv2
-rw-r--r--chapter09/pom.xml2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/GraphEvaluator.java362
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/GraphVisualizer.java12
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/IterPeeps.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/Parser.java20
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/Utils.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/AddNode.java26
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/BoolNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/ConstantNode.java8
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/DivNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/IfNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/LoopNode.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/MinusNode.java6
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/MulNode.java6
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/MultiNode.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/Node.java50
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/NotNode.java6
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/PhiNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/ProjNode.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/RegionNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/ReturnNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/ScopeNode.java8
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/StartNode.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/StopNode.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java2
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/type/Type.java18
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/type/TypeInteger.java4
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/type/TypeTuple.java6
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter01Test.java4
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter02Test.java8
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter03Test.java4
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter04Test.java10
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter05Test.java40
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter06Test.java2
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter07Test.java5
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter08Test.java10
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/Chapter09Test.java6
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java340
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java276
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java288
-rw-r--r--chapter09/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java912
47 files changed, 1265 insertions, 1266 deletions
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 @@
<artifactId>chapter09</artifactId>
<packaging>jar</packaging>
<name>Chapter 9</name>
-</project> \ No newline at end of file
+</project>
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<Node, Long> 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<Node, Long> 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 <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
*/
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 <em>def</em> into a Node. Keeps the edges correct, by removing
* the corresponding <em>use->def</em> edge. This may make the original
* <em>def</em> go dead. This function is co-recursive with {@link #kill}.
* <p>
-
+
* This method is the normal path for altering a Node, because it does the
* proper default edge maintenance. It also <em>immediately</em> 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 <em>uses</em>, by setting all of its <em>defs</em>
* 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
* <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
@@ -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<Node,Node> 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> E _walk( Function<Node,E> 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 <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
- * <p>
+ * <p>
* The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
* <p>
* 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<Type,Type> INTERN = new HashMap<>();
@@ -59,11 +59,11 @@ public class Type {
// Factory method which interns "this"
protected <T extends Type> 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<Throwable> 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<String> 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<Throwable> 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<String> 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> 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<o1.length; i++) {
- if (!isExceptionFromSameCause(o1[i], o2[i])) return false; // Different suppression
- }
- return true;
- }
-
- /**
- * Parse script with peepholes enabled or disabled
- */
- public static StopNode parse(String script, boolean runPeeps) {
- var err = System.err;
- var out = System.out;
- try {
- System.setErr(NULL_PRINT_STREAM);
- System.setOut(NULL_PRINT_STREAM);
- try {
- set_MID_ASSERT.invokeExact(false);
- } catch (Throwable e) {
- throw rethrow(e);
- }
- var parser = new Parser(script);
- Node._disablePeephole = !runPeeps;
- var stop = parser.parse();
- return runPeeps ? stop.iterate() : stop;
- } finally {
- NodeWalkVisit.clear();
- System.setErr(err);
- System.setOut(out);
- }
- }
-
-}
+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> 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<o1.length; i++) {
+ if (!isExceptionFromSameCause(o1[i], o2[i])) return false; // Different suppression
+ }
+ return true;
+ }
+
+ /**
+ * Parse script with peepholes enabled or disabled
+ */
+ public static StopNode parse(String script, boolean runPeeps) {
+ var err = System.err;
+ var out = System.out;
+ try {
+ System.setErr(NULL_PRINT_STREAM);
+ System.setOut(NULL_PRINT_STREAM);
+ try {
+ set_MID_ASSERT.invokeExact(false);
+ } catch (Throwable e) {
+ throw rethrow(e);
+ }
+ var parser = new Parser(script);
+ Node._disablePeephole = !runPeeps;
+ var stop = parser.parse();
+ return runPeeps ? stop.iterate() : stop;
+ } finally {
+ NodeWalkVisit.clear();
+ System.setErr(err);
+ System.setOut(out);
+ }
+ }
+
+}
diff --git a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
index 38a4531b..2ef0a3fd 100644
--- a/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
+++ b/chapter09/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
@@ -1,144 +1,144 @@
-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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<<max;
- var num = random.nextInt(n);
- for (int i=max-1; i>=0; i--) {
- if ((num & (1<<i))!=0) return max-i-1;
- }
- return max;
- }
-
- /**
- * Helper function to print indentation.
- * @return sb for chaining
- */
- private StringBuilder printIndentation() {
- return sb.repeat(' ', indentation);
- }
-
- /**
- * Generate a random name for variables. This might be invalid when it is a keyword.
- * @return The random name generated
- */
- private StringBuilder getRandomName() {
- int len = random.nextInt(30) + 1;
- StringBuilder sb = new StringBuilder(len);
- sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()-10)));
- for (int i=1; i<len; i++)
- sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
- return sb;
- }
-
- /**
- * Generate a variable name. This might be new random name or a name from an outer scope.
- * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope.
- * @return The variable name.
- */
- private String getVarName() {
- if (currScopeStart > 0 && random.nextInt(10) > 7) {
- // Use a variable outside the current scope.
- var v = variables.get(random.nextInt(currScopeStart));
- if (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<num; i++) {
- printIndentation();
- var stop = genStatement();
- sb.append("\n");
- if ((stop & FLAG_STOP) != 0) return stop;
- }
- return 0;
- }
-
- /**
- * Generate a single statement
- * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
- */
- public int genStatement() {
- return switch (random.nextInt(10)) {
- case 1 -> 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<String> 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<String> 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<<max;
+ var num = random.nextInt(n);
+ for (int i=max-1; i>=0; i--) {
+ if ((num & (1<<i))!=0) return max-i-1;
+ }
+ return max;
+ }
+
+ /**
+ * Helper function to print indentation.
+ * @return sb for chaining
+ */
+ private StringBuilder printIndentation() {
+ return sb.repeat(' ', indentation);
+ }
+
+ /**
+ * Generate a random name for variables. This might be invalid when it is a keyword.
+ * @return The random name generated
+ */
+ private StringBuilder getRandomName() {
+ int len = random.nextInt(30) + 1;
+ StringBuilder sb = new StringBuilder(len);
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()-10)));
+ for (int i=1; i<len; i++)
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ return sb;
+ }
+
+ /**
+ * Generate a variable name. This might be new random name or a name from an outer scope.
+ * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope.
+ * @return The variable name.
+ */
+ private String getVarName() {
+ if (currScopeStart > 0 && random.nextInt(10) > 7) {
+ // Use a variable outside the current scope.
+ var v = variables.get(random.nextInt(currScopeStart));
+ if (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<num; i++) {
+ printIndentation();
+ var stop = genStatement();
+ sb.append("\n");
+ if ((stop & FLAG_STOP) != 0) return stop;
+ }
+ return 0;
+ }
+
+ /**
+ * Generate a single statement
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genStatement() {
+ return switch (random.nextInt(10)) {
+ case 1 -> 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())));
+ }
+ }
+
+}