diff options
| author | Thalia Archibald <thalia@archibald.dev> | 2024-03-03 17:55:47 +0000 |
|---|---|---|
| committer | Thalia Archibald <thalia@archibald.dev> | 2024-10-23 17:51:09 -0700 |
| commit | 9ed409df4516e0089afbc7d5ccf9b029d431be04 (patch) | |
| tree | c0ae90716b9551dc0e58d0ec52f5e4e91bb1152f | |
| parent | 425f05826799e9da06a630187d2ad05c656266a1 (diff) | |
| download | Simple-linear-chapter09.tar.gz Simple-linear-chapter09.zip | |
Chapter 9: Global Value Numbering and Iterative Peepholeslinear-chapter09
Co-authored-by: Cliff Click <cliffc@acm.org>
Co-authored-by: XmiliaH <45106915+XmiliaH@users.noreply.github.com>
Co-authored-by: Robert Obkircher <robert.obkircher@gmail.com>
Co-authored-by: Dibyendu Majumdar <mobile@majumdar.org.uk>
Co-authored-by: Darrick Wiebe <darrickw@untether.ai>
55 files changed, 3010 insertions, 297 deletions
@@ -66,7 +66,13 @@ $(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" tests: $(default_targets) - $(JVM) org.junit.runner.JUnitCore $(test_cp) + @echo "testing " $(test_cp) + @$(JVM) org.junit.runner.JUnitCore $(test_cp) + +fuzzer: $(default_targets) + @echo "testing " $(test_cp) + @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap + .PHONY: clean clean: @@ -36,7 +36,7 @@ The Simple language will be styled after a subset of C or Java * [Chapter 6](docs/chapter06/README.md): Peephole optimization around dead control flow. * [Chapter 7](docs/chapter07/README.md): `while` statement. Looping construct - eager phi approach. * [Chapter 8](docs/chapter08/README.md): Looping construct continued, lazy phi creation, `break` and `continue` statements. -* Chapter 9: Global Value Numbering. Iterative peepholes to fixpoint. Worklists. +* [Chapter 9](docs/chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists. * Chapter 10: User defined Struct types. Memory effects: general memory edges in SSA. Equivalence class aliasing. Null pointer analysis. Peephole optimization around load-after-store/store-after-store. * Chapter 11: Global Code Motion - Scheduling. * Chapter 12: Float type. diff --git a/docs/chapter09/09-grammar.md b/docs/chapter09/09-grammar.md new file mode 100644 index 00000000..4c61bfd7 --- /dev/null +++ b/docs/chapter09/09-grammar.md @@ -0,0 +1,98 @@ +# Grammar for Chapter 8 + +```antlrv4 +grammar SimpleLanguage; + +program + : statement+ EOF + ; + +statement + : returnStatement + | declStatement + | blockStatment + | expressionStatement + | ifStatement + | whileStatement + | breakStatement + | continueStatment + | metaStatement + ; + +whileStatement + : 'while' '(' expression ')' statement + ; + +breakStatement + : 'break' ';' + ; + +continueStatement + : 'continue' ';' + ; + +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + +metaStatement + : '#showGraph' ';' + ; + +expressionStatement + : IDENTIFIER '=' expression ';' + ; + +blockStatement + : '{' statement+ '}' + ; + +declStatement + : 'int' IDENTIFIER '=' expression ';' + ; + +returnStatement + : 'return' expression ';' + ; + +expression + : comparisonExpression + ; + +comparisonExpression + : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-') unaryExpression + | primaryExpression + ; + +primaryExpression + : IDENTIFIER + | INTEGER_LITERAL + | 'true' + | 'false' + | '(' expression ')' + ; + +INTEGER_LITERAL + : [1-9][0-9]* + | [0] + ; + +IDENTIFIER + : NON_DIGIT (NON_DIGIT | DIGIT)* + ; + +NON_DIGIT: [a-zA-Z_]; +DEC_DIGIT: [0-9]; +``` diff --git a/docs/chapter09/09-graph1.gv b/docs/chapter09/09-graph1.gv new file mode 100644 index 00000000..e6d1851a --- /dev/null +++ b/docs/chapter09/09-graph1.gv @@ -0,0 +1,67 @@ +digraph chapter08 { +/* +int x = arg + arg; +if(arg < 10) { + return arg + arg; +} +else { + x = x + 1; +} +return x; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_7 [ label="2" ]; + Mul8 [ label="*" ]; + Con_9 [ label="10" ]; + LT10 [ label="<" ]; + If11 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">If</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Con_16 [ label="2" ]; + Mul17 [ label="*" ]; + Return18 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Con_20 [ label="1" ]; + Add21 [ label="+" ]; + Return24 [ shape=box style=filled fillcolor=yellow label="Return" ]; + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return18[taillabel=0 color=red]; + Stop3 -> Return24[taillabel=1 color=red]; + Mul8 -> Start2:p1[taillabel=1]; + Mul8 -> Con_7[taillabel=2]; + LT10 -> Start2:p1[taillabel=1]; + LT10 -> Con_9[taillabel=2]; + If11 -> Start2:p0[taillabel=0 color=red]; + If11 -> LT10[taillabel=1]; + Mul17 -> Start2:p1[taillabel=1]; + Mul17 -> Con_16[taillabel=2]; + Return18 -> If11:p0[taillabel=0 color=red]; + Return18 -> Mul17[taillabel=1]; + Add21 -> Mul8[taillabel=1]; + Add21 -> Con_20[taillabel=2]; + Return24 -> If11:p1[taillabel=0 color=red]; + Return24 -> Add21[taillabel=1]; +} diff --git a/docs/chapter09/09-graph1.svg b/docs/chapter09/09-graph1.svg new file mode 100644 index 00000000..fce8d53c --- /dev/null +++ b/docs/chapter09/09-graph1.svg @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter08 Pages: 1 --> +<svg width="350pt" height="420pt" + viewBox="0.00 0.00 350.00 420.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 416)"> +<title>chapter08</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-416 346,-416 346,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-404 334,-404 334,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="56,-367 56,-392 137,-392 137,-367 56,-367"/> +<polygon fill="none" stroke="black" points="56,-367 56,-392 137,-392 137,-367 56,-367"/> +<text text-anchor="start" x="78" y="-375.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="56,-336 56,-367 137,-367 137,-336 56,-336"/> +<polygon fill="yellow" stroke="transparent" points="61,-341 61,-362 101,-362 101,-341 61,-341"/> +<polygon fill="none" stroke="black" points="61,-341 61,-362 101,-362 101,-341 61,-341"/> +<text text-anchor="start" x="64" y="-347.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="101,-341 101,-362 132,-362 132,-341 101,-341"/> +<text text-anchor="start" x="104" y="-347.8" font-family="Times,serif" font-size="14.00">arg</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="175,-52 121,-52 121,-16 175,-16 175,-52"/> +<text text-anchor="middle" x="148" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return18 --> +<g id="node10" class="node"> +<title>Return18</title> +<polygon fill="yellow" stroke="black" points="139,-124 73,-124 73,-88 139,-88 139,-124"/> +<text text-anchor="middle" x="106" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return18 --> +<g id="edge1" class="edge"> +<title>Stop3->Return18</title> +<path fill="none" stroke="red" d="M137.62,-52.3C132.74,-60.44 126.81,-70.31 121.42,-79.3"/> +<polygon fill="red" stroke="red" points="118.41,-77.52 116.26,-87.9 124.41,-81.12 118.41,-77.52"/> +<text text-anchor="middle" x="134.62" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return24 --> +<g id="node13" class="node"> +<title>Return24</title> +<polygon fill="yellow" stroke="black" points="223,-124 157,-124 157,-88 223,-88 223,-124"/> +<text text-anchor="middle" x="190" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return24 --> +<g id="edge2" class="edge"> +<title>Stop3->Return24</title> +<path fill="none" stroke="red" d="M158.38,-52.3C163.26,-60.44 169.19,-70.31 174.58,-79.3"/> +<polygon fill="red" stroke="red" points="171.59,-81.12 179.74,-87.9 177.59,-77.52 171.59,-81.12"/> +<text text-anchor="middle" x="155.38" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_7 --> +<g id="node3" class="node"> +<title>Con_7</title> +<ellipse fill="none" stroke="black" cx="262" cy="-364" rx="27" ry="18"/> +<text text-anchor="middle" x="262" y="-360.3" font-family="Times,serif" font-size="14.00">2</text> +</g> +<!-- Mul8 --> +<g id="node4" class="node"> +<title>Mul8</title> +<ellipse fill="none" stroke="black" cx="227" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="227" y="-274.3" font-family="Times,serif" font-size="14.00">*</text> +</g> +<!-- Mul8->Start2 --> +<g id="edge3" class="edge"> +<title>Mul8->Start2:p1</title> +<path fill="none" stroke="black" d="M208.6,-291.54C193.58,-301.96 172,-317.39 154,-332 147.74,-337.08 146.05,-343.45 142.33,-347.37"/> +<polygon fill="black" stroke="black" points="141.05,-344.11 133,-351 143.59,-350.64 141.05,-344.11"/> +<text text-anchor="middle" x="205.6" y="-294.14" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Mul8->Con_7 --> +<g id="edge4" class="edge"> +<title>Mul8->Con_7</title> +<path fill="none" stroke="black" d="M233.91,-295.6C238.85,-307.45 245.59,-323.62 251.23,-337.15"/> +<polygon fill="black" stroke="black" points="248.02,-338.55 255.1,-346.44 254.48,-335.86 248.02,-338.55"/> +<text text-anchor="middle" x="230.91" y="-298.2" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Con_9 --> +<g id="node5" class="node"> +<title>Con_9</title> +<ellipse fill="none" stroke="black" cx="190" cy="-364" rx="27" ry="18"/> +<text text-anchor="middle" x="190" y="-360.3" font-family="Times,serif" font-size="14.00">10</text> +</g> +<!-- LT10 --> +<g id="node6" class="node"> +<title>LT10</title> +<ellipse fill="none" stroke="black" cx="155" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="155" y="-274.3" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT10->Start2 --> +<g id="edge5" class="edge"> +<title>LT10->Start2:p1</title> +<path fill="none" stroke="black" d="M139.98,-293.02C131.35,-302.35 121.57,-315.51 118.21,-329.88"/> +<polygon fill="black" stroke="black" points="114.71,-329.65 117,-340 121.66,-330.49 114.71,-329.65"/> +<text text-anchor="middle" x="136.98" y="-295.62" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- LT10->Con_9 --> +<g id="edge6" class="edge"> +<title>LT10->Con_9</title> +<path fill="none" stroke="black" d="M161.91,-295.6C166.85,-307.45 173.59,-323.62 179.23,-337.15"/> +<polygon fill="black" stroke="black" points="176.02,-338.55 183.1,-346.44 182.48,-335.86 176.02,-338.55"/> +<text text-anchor="middle" x="158.91" y="-298.2" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If11 --> +<g id="node7" class="node"> +<title>If11</title> +<polygon fill="yellow" stroke="transparent" points="102,-195 102,-220 196,-220 196,-195 102,-195"/> +<polygon fill="none" stroke="black" points="102,-195 102,-220 196,-220 196,-195 102,-195"/> +<text text-anchor="start" x="143.5" y="-203.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="102,-164 102,-195 196,-195 196,-164 102,-164"/> +<polygon fill="yellow" stroke="transparent" points="107,-169 107,-190 147,-190 147,-169 107,-169"/> +<polygon fill="none" stroke="black" points="107,-169 107,-190 147,-190 147,-169 107,-169"/> +<text text-anchor="start" x="110" y="-175.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="147,-169 147,-190 191,-190 191,-169 147,-169"/> +<polygon fill="none" stroke="black" points="147,-169 147,-190 191,-190 191,-169 147,-169"/> +<text text-anchor="start" x="150" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If11->Start2 --> +<g id="edge7" class="edge"> +<title>If11->Start2:p0</title> +<path fill="none" stroke="red" d="M133.74,-224.23C117.53,-257.6 93.05,-308.62 84.28,-330.23"/> +<polygon fill="red" stroke="red" points="80.86,-329.41 81,-340 87.5,-331.63 80.86,-329.41"/> +<text text-anchor="middle" x="130.74" y="-226.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If11->LT10 --> +<g id="edge8" class="edge"> +<title>If11->LT10</title> +<path fill="none" stroke="black" d="M151.23,-224.28C151.83,-232.58 152.46,-241.48 153.04,-249.56"/> +<polygon fill="black" stroke="black" points="149.55,-249.87 153.76,-259.6 156.54,-249.37 149.55,-249.87"/> +<text text-anchor="middle" x="148.23" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_16 --> +<g id="node8" class="node"> +<title>Con_16</title> +<ellipse fill="none" stroke="black" cx="43" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-274.3" font-family="Times,serif" font-size="14.00">2</text> +</g> +<!-- Mul17 --> +<g id="node9" class="node"> +<title>Mul17</title> +<ellipse fill="none" stroke="black" cx="49" cy="-192" rx="27" ry="18"/> +<text text-anchor="middle" x="49" y="-188.3" font-family="Times,serif" font-size="14.00">*</text> +</g> +<!-- Mul17->Start2 --> +<g id="edge9" class="edge"> +<title>Mul17->Start2:p1</title> +<path fill="none" stroke="black" d="M58.46,-208.9C75.17,-237.54 108.83,-297.79 115.74,-329.91"/> +<polygon fill="black" stroke="black" points="112.29,-330.51 117,-340 119.24,-329.64 112.29,-330.51"/> +<text text-anchor="middle" x="55.46" y="-211.5" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Mul17->Con_16 --> +<g id="edge10" class="edge"> +<title>Mul17->Con_16</title> +<path fill="none" stroke="black" d="M47.79,-210C46.98,-221.31 45.9,-236.42 44.97,-249.43"/> +<polygon fill="black" stroke="black" points="41.45,-249.51 44.23,-259.74 48.44,-250.01 41.45,-249.51"/> +<text text-anchor="middle" x="44.79" y="-212.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return18->If11 --> +<g id="edge11" class="edge"> +<title>Return18->If11:p0</title> +<path fill="none" stroke="red" d="M115.27,-124.08C119.67,-133.41 124.35,-145.54 126.19,-157.76"/> +<polygon fill="red" stroke="red" points="122.72,-158.31 127,-168 129.7,-157.76 122.72,-158.31"/> +<text text-anchor="middle" x="112.27" y="-126.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return18->Mul17 --> +<g id="edge12" class="edge"> +<title>Return18->Mul17</title> +<path fill="none" stroke="black" d="M94.19,-124.4C85.84,-136.7 74.55,-153.35 65.34,-166.92"/> +<polygon fill="black" stroke="black" points="62.36,-165.08 59.64,-175.32 68.15,-169.01 62.36,-165.08"/> +<text text-anchor="middle" x="91.19" y="-127" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_20 --> +<g id="node11" class="node"> +<title>Con_20</title> +<ellipse fill="none" stroke="black" cx="299" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="299" y="-274.3" font-family="Times,serif" font-size="14.00">1</text> +</g> +<!-- Add21 --> +<g id="node12" class="node"> +<title>Add21</title> +<ellipse fill="none" stroke="black" cx="249" cy="-192" rx="27" ry="18"/> +<text text-anchor="middle" x="249" y="-188.3" font-family="Times,serif" font-size="14.00">+</text> +</g> +<!-- Add21->Mul8 --> +<g id="edge13" class="edge"> +<title>Add21->Mul8</title> +<path fill="none" stroke="black" d="M244.55,-210C241.54,-221.5 237.49,-236.93 234.05,-250.08"/> +<polygon fill="black" stroke="black" points="230.59,-249.47 231.45,-260.03 237.36,-251.24 230.59,-249.47"/> +<text text-anchor="middle" x="241.55" y="-212.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Add21->Con_20 --> +<g id="edge14" class="edge"> +<title>Add21->Con_20</title> +<path fill="none" stroke="black" d="M258.64,-209.2C265.9,-221.4 275.99,-238.34 284.26,-252.24"/> +<polygon fill="black" stroke="black" points="281.27,-254.05 289.39,-260.85 287.28,-250.47 281.27,-254.05"/> +<text text-anchor="middle" x="255.64" y="-211.8" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return24->If11 --> +<g id="edge15" class="edge"> +<title>Return24->If11:p1</title> +<path fill="none" stroke="red" d="M180.73,-124.08C176.33,-133.41 171.65,-145.54 169.81,-157.76"/> +<polygon fill="red" stroke="red" points="166.3,-157.76 169,-168 173.28,-158.31 166.3,-157.76"/> +<text text-anchor="middle" x="177.73" y="-126.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return24->Add21 --> +<g id="edge16" class="edge"> +<title>Return24->Add21</title> +<path fill="none" stroke="black" d="M202.22,-124.4C210.86,-136.7 222.55,-153.35 232.09,-166.92"/> +<polygon fill="black" stroke="black" points="229.37,-169.15 237.98,-175.32 235.1,-165.12 229.37,-169.15"/> +<text text-anchor="middle" x="199.22" y="-127" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/09-graph2.gv b/docs/chapter09/09-graph2.gv new file mode 100644 index 00000000..1a1f7a29 --- /dev/null +++ b/docs/chapter09/09-graph2.gv @@ -0,0 +1,63 @@ +digraph chapter09 { +/* +int x = arg + arg; +if(arg < 10) { + return arg + arg; +} +else { + x = x + 1; +} +return x; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_7 [ label="#2" ]; + Mul8 [ label="*" ]; + LT9 [ label="<" ]; + Con_10 [ label="#10" ]; + If11 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">If</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Return18 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Add20 [ label="+" ]; + Con_21 [ label="#1" ]; + Return24 [ shape=box style=filled fillcolor=yellow label="Return" ]; + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return18[taillabel=0 color=red]; + Stop3 -> Return24[taillabel=1 color=red]; + Mul8 -> Start2:p1[taillabel=1]; + Mul8 -> Con_7[taillabel=2]; + LT9 -> Start2:p1[taillabel=1]; + LT9 -> Con_10[taillabel=2]; + If11 -> Start2:p0[taillabel=0 color=red]; + If11 -> LT9[taillabel=1]; + Return18 -> If11:p0[taillabel=0 color=red]; + Return18 -> Mul8[taillabel=1]; + Add20 -> Mul8[taillabel=1]; + Add20 -> Con_21[taillabel=2]; + Return24 -> If11:p1[taillabel=0 color=red]; + Return24 -> Add20[taillabel=1]; +} diff --git a/docs/chapter09/09-graph2.svg b/docs/chapter09/09-graph2.svg new file mode 100644 index 00000000..111ec172 --- /dev/null +++ b/docs/chapter09/09-graph2.svg @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter09 Pages: 1 --> +<svg width="289pt" height="420pt" + viewBox="0.00 0.00 289.00 420.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 416)"> +<title>chapter09</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-416 285,-416 285,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-404 273,-404 273,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="97,-367 97,-392 178,-392 178,-367 97,-367"/> +<polygon fill="none" stroke="black" points="97,-367 97,-392 178,-392 178,-367 97,-367"/> +<text text-anchor="start" x="119" y="-375.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="97,-336 97,-367 178,-367 178,-336 97,-336"/> +<polygon fill="yellow" stroke="transparent" points="102,-341 102,-362 142,-362 142,-341 102,-341"/> +<polygon fill="none" stroke="black" points="102,-341 102,-362 142,-362 142,-341 102,-341"/> +<text text-anchor="start" x="105" y="-347.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="142,-341 142,-362 173,-362 173,-341 142,-341"/> +<text text-anchor="start" x="145" y="-347.8" font-family="Times,serif" font-size="14.00">arg</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="164,-52 110,-52 110,-16 164,-16 164,-52"/> +<text text-anchor="middle" x="137" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return18 --> +<g id="node8" class="node"> +<title>Return18</title> +<polygon fill="yellow" stroke="black" points="128,-124 62,-124 62,-88 128,-88 128,-124"/> +<text text-anchor="middle" x="95" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return18 --> +<g id="edge1" class="edge"> +<title>Stop3->Return18</title> +<path fill="none" stroke="red" d="M126.62,-52.3C121.74,-60.44 115.81,-70.31 110.42,-79.3"/> +<polygon fill="red" stroke="red" points="107.41,-77.52 105.26,-87.9 113.41,-81.12 107.41,-77.52"/> +<text text-anchor="middle" x="123.62" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return24 --> +<g id="node11" class="node"> +<title>Return24</title> +<polygon fill="yellow" stroke="black" points="212,-124 146,-124 146,-88 212,-88 212,-124"/> +<text text-anchor="middle" x="179" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return24 --> +<g id="edge2" class="edge"> +<title>Stop3->Return24</title> +<path fill="none" stroke="red" d="M147.38,-52.3C152.26,-60.44 158.19,-70.31 163.58,-79.3"/> +<polygon fill="red" stroke="red" points="160.59,-81.12 168.74,-87.9 166.59,-77.52 160.59,-81.12"/> +<text text-anchor="middle" x="144.38" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_7 --> +<g id="node3" class="node"> +<title>Con_7</title> +<ellipse fill="none" stroke="black" cx="43" cy="-364" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-360.3" font-family="Times,serif" font-size="14.00">#2</text> +</g> +<!-- Mul8 --> +<g id="node4" class="node"> +<title>Mul8</title> +<ellipse fill="none" stroke="black" cx="94" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="94" y="-274.3" font-family="Times,serif" font-size="14.00">*</text> +</g> +<!-- Mul8->Start2 --> +<g id="edge3" class="edge"> +<title>Mul8->Start2:p1</title> +<path fill="none" stroke="black" d="M116.03,-288.88C131.86,-297.42 151.32,-311.35 156.62,-330.09"/> +<polygon fill="black" stroke="black" points="153.15,-330.58 158,-340 160.08,-329.61 153.15,-330.58"/> +<text text-anchor="middle" x="119.03" y="-291.48" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Mul8->Con_7 --> +<g id="edge4" class="edge"> +<title>Mul8->Con_7</title> +<path fill="none" stroke="black" d="M84.17,-295.2C76.71,-307.48 66.34,-324.56 57.87,-338.51"/> +<polygon fill="black" stroke="black" points="54.83,-336.77 52.63,-347.14 60.81,-340.41 54.83,-336.77"/> +<text text-anchor="middle" x="81.17" y="-297.8" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- LT9 --> +<g id="node5" class="node"> +<title>LT9</title> +<ellipse fill="none" stroke="black" cx="166" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="166" y="-274.3" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT9->Start2 --> +<g id="edge5" class="edge"> +<title>LT9->Start2:p1</title> +<path fill="none" stroke="black" d="M162.56,-296.01C160.87,-305.54 159.04,-317.9 158.32,-329.98"/> +<polygon fill="black" stroke="black" points="154.82,-329.89 158,-340 161.81,-330.12 154.82,-329.89"/> +<text text-anchor="middle" x="159.56" y="-298.61" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_10 --> +<g id="node6" class="node"> +<title>Con_10</title> +<ellipse fill="none" stroke="black" cx="234" cy="-364" rx="30.59" ry="18"/> +<text text-anchor="middle" x="234" y="-360.3" font-family="Times,serif" font-size="14.00">#10</text> +</g> +<!-- LT9->Con_10 --> +<g id="edge6" class="edge"> +<title>LT9->Con_10</title> +<path fill="none" stroke="black" d="M178.47,-294.41C188.66,-307 203.24,-325 214.9,-339.41"/> +<polygon fill="black" stroke="black" points="212.36,-341.84 221.38,-347.41 217.8,-337.43 212.36,-341.84"/> +<text text-anchor="middle" x="175.47" y="-297.01" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If11 --> +<g id="node7" class="node"> +<title>If11</title> +<polygon fill="yellow" stroke="transparent" points="91,-195 91,-220 185,-220 185,-195 91,-195"/> +<polygon fill="none" stroke="black" points="91,-195 91,-220 185,-220 185,-195 91,-195"/> +<text text-anchor="start" x="132.5" y="-203.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="91,-164 91,-195 185,-195 185,-164 91,-164"/> +<polygon fill="yellow" stroke="transparent" points="96,-169 96,-190 136,-190 136,-169 96,-169"/> +<polygon fill="none" stroke="black" points="96,-169 96,-190 136,-190 136,-169 96,-169"/> +<text text-anchor="start" x="99" y="-175.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="136,-169 136,-190 180,-190 180,-169 136,-169"/> +<polygon fill="none" stroke="black" points="136,-169 136,-190 180,-190 180,-169 136,-169"/> +<text text-anchor="start" x="139" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If11->Start2 --> +<g id="edge7" class="edge"> +<title>If11->Start2:p0</title> +<path fill="none" stroke="red" d="M87.32,-224.07C75.52,-233.97 64.51,-246.05 58,-260 51.23,-274.5 52.79,-280.87 58,-296 59.97,-301.73 80.37,-332.08 92.9,-345.08"/> +<polygon fill="red" stroke="red" points="90.86,-347.92 101,-351 94.99,-342.27 90.86,-347.92"/> +<text text-anchor="middle" x="84.32" y="-226.67" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If11->LT9 --> +<g id="edge8" class="edge"> +<title>If11->LT9</title> +<path fill="none" stroke="black" d="M148.43,-224.28C151.3,-232.9 154.39,-242.17 157.16,-250.49"/> +<polygon fill="black" stroke="black" points="153.87,-251.69 160.36,-260.07 160.51,-249.48 153.87,-251.69"/> +<text text-anchor="middle" x="145.43" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return18->Mul8 --> +<g id="edge10" class="edge"> +<title>Return18->Mul8</title> +<path fill="none" stroke="black" d="M86.28,-124.17C81.63,-134.31 76.38,-147.56 74,-160 68.67,-187.94 68.9,-196.02 74,-224 75.67,-233.18 78.85,-242.83 82.19,-251.35"/> +<polygon fill="black" stroke="black" points="79.03,-252.85 86.11,-260.73 85.49,-250.15 79.03,-252.85"/> +<text text-anchor="middle" x="83.28" y="-126.77" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return18->If11 --> +<g id="edge9" class="edge"> +<title>Return18->If11:p0</title> +<path fill="none" stroke="red" d="M104.27,-124.08C108.67,-133.41 113.35,-145.54 115.19,-157.76"/> +<polygon fill="red" stroke="red" points="111.72,-158.31 116,-168 118.7,-157.76 111.72,-158.31"/> +<text text-anchor="middle" x="101.27" y="-126.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Add20 --> +<g id="node9" class="node"> +<title>Add20</title> +<ellipse fill="none" stroke="black" cx="238" cy="-192" rx="27" ry="18"/> +<text text-anchor="middle" x="238" y="-188.3" font-family="Times,serif" font-size="14.00">+</text> +</g> +<!-- Add20->Mul8 --> +<g id="edge11" class="edge"> +<title>Add20->Mul8</title> +<path fill="none" stroke="black" d="M223.27,-207.18C217,-212.81 209.43,-219.09 202,-224 194.96,-228.65 153.81,-248.53 124.4,-262.57"/> +<polygon fill="black" stroke="black" points="122.84,-259.44 115.32,-266.9 125.85,-265.75 122.84,-259.44"/> +<text text-anchor="middle" x="220.27" y="-209.78" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_21 --> +<g id="node10" class="node"> +<title>Con_21</title> +<ellipse fill="none" stroke="black" cx="238" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="238" y="-274.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Add20->Con_21 --> +<g id="edge12" class="edge"> +<title>Add20->Con_21</title> +<path fill="none" stroke="black" d="M238,-210.4C238,-221.74 238,-236.77 238,-249.68"/> +<polygon fill="black" stroke="black" points="234.5,-249.9 238,-259.9 241.5,-249.9 234.5,-249.9"/> +<text text-anchor="middle" x="235" y="-213" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return24->If11 --> +<g id="edge13" class="edge"> +<title>Return24->If11:p1</title> +<path fill="none" stroke="red" d="M169.73,-124.08C165.33,-133.41 160.65,-145.54 158.81,-157.76"/> +<polygon fill="red" stroke="red" points="155.3,-157.76 158,-168 162.28,-158.31 155.3,-157.76"/> +<text text-anchor="middle" x="166.73" y="-126.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return24->Add20 --> +<g id="edge14" class="edge"> +<title>Return24->Add20</title> +<path fill="none" stroke="black" d="M191.22,-124.4C199.86,-136.7 211.55,-153.35 221.09,-166.92"/> +<polygon fill="black" stroke="black" points="218.37,-169.15 226.98,-175.32 224.1,-165.12 218.37,-169.15"/> +<text text-anchor="middle" x="188.22" y="-127" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/09-graph3.gv b/docs/chapter09/09-graph3.gv new file mode 100644 index 00000000..37bb1c53 --- /dev/null +++ b/docs/chapter09/09-graph3.gv @@ -0,0 +1,37 @@ +digraph chapter08 { +/* +return arg*arg-arg*arg; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Mul6 [ label="*" ]; + Mul7 [ label="*" ]; + Sub8 [ label="-" ]; + Return9 [ shape=box style=filled fillcolor=yellow label="Return" ]; + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return9[taillabel=0 color=red]; + Mul6 -> Start2:p1[taillabel=1]; + Mul6 -> Start2:p1[taillabel=2]; + Mul7 -> Start2:p1[taillabel=1]; + Mul7 -> Start2:p1[taillabel=2]; + Sub8 -> Mul6[taillabel=1]; + Sub8 -> Mul7[taillabel=2]; + Return9 -> Start2:p0[taillabel=0 color=red]; + Return9 -> Sub8[taillabel=1]; +} diff --git a/docs/chapter09/09-graph3.svg b/docs/chapter09/09-graph3.svg new file mode 100644 index 00000000..f26c96e8 --- /dev/null +++ b/docs/chapter09/09-graph3.svg @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter08 Pages: 1 --> +<svg width="186pt" height="392pt" + viewBox="0.00 0.00 186.00 392.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 388)"> +<title>chapter08</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-388 182,-388 182,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-376 170,-376 170,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="24,-339 24,-364 105,-364 105,-339 24,-339"/> +<polygon fill="none" stroke="black" points="24,-339 24,-364 105,-364 105,-339 24,-339"/> +<text text-anchor="start" x="46" y="-347.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="24,-308 24,-339 105,-339 105,-308 24,-308"/> +<polygon fill="yellow" stroke="transparent" points="29,-313 29,-334 69,-334 69,-313 29,-313"/> +<polygon fill="none" stroke="black" points="29,-313 29,-334 69,-334 69,-313 29,-313"/> +<text text-anchor="start" x="32" y="-319.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="69,-313 69,-334 100,-334 100,-313 69,-313"/> +<text text-anchor="start" x="72" y="-319.8" font-family="Times,serif" font-size="14.00">arg</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="83,-52 29,-52 29,-16 83,-16 83,-52"/> +<text text-anchor="middle" x="56" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return9 --> +<g id="node6" class="node"> +<title>Return9</title> +<polygon fill="yellow" stroke="black" points="89,-124 23,-124 23,-88 89,-88 89,-124"/> +<text text-anchor="middle" x="56" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return9 --> +<g id="edge1" class="edge"> +<title>Stop3->Return9</title> +<path fill="none" stroke="red" d="M56,-52.3C56,-60.02 56,-69.29 56,-77.89"/> +<polygon fill="red" stroke="red" points="52.5,-77.9 56,-87.9 59.5,-77.9 52.5,-77.9"/> +<text text-anchor="middle" x="53" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Mul6 --> +<g id="node3" class="node"> +<title>Mul6</title> +<ellipse fill="none" stroke="black" cx="63" cy="-250" rx="27" ry="18"/> +<text text-anchor="middle" x="63" y="-246.3" font-family="Times,serif" font-size="14.00">*</text> +</g> +<!-- Mul6->Start2 --> +<g id="edge2" class="edge"> +<title>Mul6->Start2:p1</title> +<path fill="none" stroke="black" d="M72.2,-267.01C76.96,-276.47 82.18,-289.06 84.17,-301.8"/> +<polygon fill="black" stroke="black" points="80.7,-302.32 85,-312 87.67,-301.75 80.7,-302.32"/> +<text text-anchor="middle" x="69.2" y="-269.61" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Mul7 --> +<g id="node4" class="node"> +<title>Mul7</title> +<ellipse fill="none" stroke="black" cx="135" cy="-250" rx="27" ry="18"/> +<text text-anchor="middle" x="135" y="-246.3" font-family="Times,serif" font-size="14.00">*</text> +</g> +<!-- Mul7->Start2 --> +<g id="edge4" class="edge"> +<title>Mul7->Start2:p1</title> +<path fill="none" stroke="black" d="M116.38,-263.15C104.61,-272.17 90.81,-285.67 86.42,-301.7"/> +<polygon fill="black" stroke="black" points="82.9,-301.62 85,-312 89.83,-302.57 82.9,-301.62"/> +<text text-anchor="middle" x="113.38" y="-265.75" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Sub8 --> +<g id="node5" class="node"> +<title>Sub8</title> +<ellipse fill="none" stroke="black" cx="63" cy="-178" rx="27" ry="18"/> +<text text-anchor="middle" x="63" y="-174.3" font-family="Times,serif" font-size="14.00">-</text> +</g> +<!-- Sub8->Mul6 --> +<g id="edge6" class="edge"> +<title>Sub8->Mul6</title> +<path fill="none" stroke="black" d="M63,-196.3C63,-204.02 63,-213.29 63,-221.89"/> +<polygon fill="black" stroke="black" points="59.5,-221.9 63,-231.9 66.5,-221.9 59.5,-221.9"/> +<text text-anchor="middle" x="60" y="-198.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Sub8->Mul7 --> +<g id="edge7" class="edge"> +<title>Sub8->Mul7</title> +<path fill="none" stroke="black" d="M77.57,-193.17C87.75,-203.06 101.52,-216.45 113.03,-227.64"/> +<polygon fill="black" stroke="black" points="110.59,-230.15 120.2,-234.62 115.47,-225.13 110.59,-230.15"/> +<text text-anchor="middle" x="74.57" y="-195.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return9->Start2 --> +<g id="edge8" class="edge"> +<title>Return9->Start2:p0</title> +<path fill="none" stroke="red" d="M43.69,-124.22C37.35,-134.16 30.27,-147.2 27,-160 15.11,-206.5 15.97,-221.28 27,-268 31.12,-285.46 43.28,-289.17 47.52,-302.01"/> +<polygon fill="red" stroke="red" points="44.08,-302.62 49,-312 51,-301.6 44.08,-302.62"/> +<text text-anchor="middle" x="40.69" y="-126.82" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return9->Sub8 --> +<g id="edge9" class="edge"> +<title>Return9->Sub8</title> +<path fill="none" stroke="black" d="M57.73,-124.3C58.5,-132.02 59.43,-141.29 60.29,-149.89"/> +<polygon fill="black" stroke="black" points="56.81,-150.29 61.29,-159.9 63.78,-149.6 56.81,-150.29"/> +<text text-anchor="middle" x="54.73" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/09-graph4.gv b/docs/chapter09/09-graph4.gv new file mode 100644 index 00000000..8886f73a --- /dev/null +++ b/docs/chapter09/09-graph4.gv @@ -0,0 +1,29 @@ +digraph chapter09 { +/* +return arg*arg-arg*arg; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_9 [ label="#0" ]; + Return10 [ shape=box style=filled fillcolor=yellow label="Return" ]; + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return10[taillabel=0 color=red]; + Return10 -> Start2:p0[taillabel=0 color=red]; + Return10 -> Con_9[taillabel=1]; +} diff --git a/docs/chapter09/09-graph4.svg b/docs/chapter09/09-graph4.svg new file mode 100644 index 00000000..4eef5414 --- /dev/null +++ b/docs/chapter09/09-graph4.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter09 Pages: 1 --> +<svg width="178pt" height="248pt" + viewBox="0.00 0.00 178.00 248.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 244)"> +<title>chapter09</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-244 174,-244 174,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-232 162,-232 162,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="24,-195 24,-220 74,-220 74,-195 24,-195"/> +<polygon fill="none" stroke="black" points="24,-195 24,-220 74,-220 74,-195 24,-195"/> +<text text-anchor="start" x="30.5" y="-203.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="24,-164 24,-195 74,-195 74,-164 24,-164"/> +<polygon fill="yellow" stroke="transparent" points="29,-169 29,-190 69,-190 69,-169 29,-169"/> +<polygon fill="none" stroke="black" points="29,-169 29,-190 69,-190 69,-169 29,-169"/> +<text text-anchor="start" x="32" y="-175.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="112,-52 58,-52 58,-16 112,-16 112,-52"/> +<text text-anchor="middle" x="85" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return10 --> +<g id="node4" class="node"> +<title>Return10</title> +<polygon fill="yellow" stroke="black" points="118,-124 52,-124 52,-88 118,-88 118,-124"/> +<text text-anchor="middle" x="85" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return10 --> +<g id="edge1" class="edge"> +<title>Stop3->Return10</title> +<path fill="none" stroke="red" d="M85,-52.3C85,-60.02 85,-69.29 85,-77.89"/> +<polygon fill="red" stroke="red" points="81.5,-77.9 85,-87.9 88.5,-77.9 81.5,-77.9"/> +<text text-anchor="middle" x="82" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_9 --> +<g id="node3" class="node"> +<title>Con_9</title> +<ellipse fill="none" stroke="black" cx="127" cy="-192" rx="27" ry="18"/> +<text text-anchor="middle" x="127" y="-188.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- Return10->Start2 --> +<g id="edge2" class="edge"> +<title>Return10->Start2:p0</title> +<path fill="none" stroke="red" d="M68.26,-124.13C60.77,-133.26 52.99,-145.23 50.13,-158.06"/> +<polygon fill="red" stroke="red" points="46.66,-157.67 49,-168 53.61,-158.46 46.66,-157.67"/> +<text text-anchor="middle" x="65.26" y="-126.73" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return10->Con_9 --> +<g id="edge3" class="edge"> +<title>Return10->Con_9</title> +<path fill="none" stroke="black" d="M93.7,-124.4C99.66,-136.31 107.65,-152.3 114.31,-165.62"/> +<polygon fill="black" stroke="black" points="111.28,-167.38 118.88,-174.76 117.54,-164.25 111.28,-167.38"/> +<text text-anchor="middle" x="90.7" y="-127" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/09-graph5.gv b/docs/chapter09/09-graph5.gv new file mode 100644 index 00000000..72274239 --- /dev/null +++ b/docs/chapter09/09-graph5.gv @@ -0,0 +1,63 @@ +digraph chapter09 { +/* +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_6 [ label="#1" ]; + Loop7 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg9 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT10 [ label="<" ]; + Con_11 [ label="#10" ]; + If12 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">If</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Add16 [ label="+" ]; + Add18 [ label="+" ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop7;Phi_arg9;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return21[taillabel=0 color=red]; + Loop7 -> Start2:p0[taillabel=1 color=red]; + Loop7 -> If12:p0[taillabel=2 color=red constraint=false]; + Phi_arg9 -> Loop7 [style=dotted taillabel=0]; + Phi_arg9 -> Start2:p1[taillabel=1]; + Phi_arg9 -> Add18[taillabel=2 constraint=false]; + LT10 -> Phi_arg9[taillabel=1]; + LT10 -> Con_11[taillabel=2]; + If12 -> Loop7[taillabel=0 color=red]; + If12 -> LT10[taillabel=1]; + Add16 -> Con_6[taillabel=1]; + Add16 -> Phi_arg9[taillabel=2]; + Add18 -> Add16[taillabel=1]; + Add18 -> Con_6[taillabel=2]; + Return21 -> If12:p1[taillabel=0 color=red]; + Return21 -> Phi_arg9[taillabel=1]; +} diff --git a/docs/chapter09/09-graph5.svg b/docs/chapter09/09-graph5.svg new file mode 100644 index 00000000..9d0698db --- /dev/null +++ b/docs/chapter09/09-graph5.svg @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter09 Pages: 1 --> +<svg width="338pt" height="492pt" + viewBox="0.00 0.00 338.00 492.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 488)"> +<title>chapter09</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-488 334,-488 334,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-476 322,-476 322,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="153,-439 153,-464 234,-464 234,-439 153,-439"/> +<polygon fill="none" stroke="black" points="153,-439 153,-464 234,-464 234,-439 153,-439"/> +<text text-anchor="start" x="175" y="-447.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="153,-408 153,-439 234,-439 234,-408 153,-408"/> +<polygon fill="yellow" stroke="transparent" points="158,-413 158,-434 198,-434 198,-413 158,-413"/> +<polygon fill="none" stroke="black" points="158,-413 158,-434 198,-434 198,-413 158,-413"/> +<text text-anchor="start" x="161" y="-419.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="198,-413 198,-434 229,-434 229,-413 198,-413"/> +<text text-anchor="start" x="201" y="-419.8" font-family="Times,serif" font-size="14.00">arg</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="271,-52 217,-52 217,-16 271,-16 271,-52"/> +<text text-anchor="middle" x="244" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return21 --> +<g id="node11" class="node"> +<title>Return21</title> +<polygon fill="yellow" stroke="black" points="277,-124 211,-124 211,-88 277,-88 277,-124"/> +<text text-anchor="middle" x="244" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return21 --> +<g id="edge1" class="edge"> +<title>Stop3->Return21</title> +<path fill="none" stroke="red" d="M244,-52.3C244,-60.02 244,-69.29 244,-77.89"/> +<polygon fill="red" stroke="red" points="240.5,-77.9 244,-87.9 247.5,-77.9 240.5,-77.9"/> +<text text-anchor="middle" x="241" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_6 --> +<g id="node3" class="node"> +<title>Con_6</title> +<ellipse fill="none" stroke="black" cx="43" cy="-350" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-346.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Loop7 --> +<g id="node4" class="node"> +<title>Loop7</title> +<polygon fill="yellow" stroke="black" points="142,-368 88,-368 88,-332 142,-332 142,-368"/> +<text text-anchor="middle" x="115" y="-346.3" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop7->Start2 --> +<g id="edge2" class="edge"> +<title>Loop7->Start2:p0</title> +<path fill="none" stroke="red" d="M117.35,-368.06C120.75,-386.19 128.75,-413.28 146.69,-420.93"/> +<polygon fill="red" stroke="red" points="146.51,-424.46 157,-423 147.89,-417.6 146.51,-424.46"/> +<text text-anchor="middle" x="114.35" y="-370.66" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If12 --> +<g id="node8" class="node"> +<title>If12</title> +<polygon fill="yellow" stroke="transparent" points="147,-195 147,-220 241,-220 241,-195 147,-195"/> +<polygon fill="none" stroke="black" points="147,-195 147,-220 241,-220 241,-195 147,-195"/> +<text text-anchor="start" x="188.5" y="-203.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="147,-164 147,-195 241,-195 241,-164 147,-164"/> +<polygon fill="yellow" stroke="transparent" points="152,-169 152,-190 192,-190 192,-169 152,-169"/> +<polygon fill="none" stroke="black" points="152,-169 152,-190 192,-190 192,-169 152,-169"/> +<text text-anchor="start" x="155" y="-175.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="192,-169 192,-190 236,-190 236,-169 192,-169"/> +<polygon fill="none" stroke="black" points="192,-169 192,-190 236,-190 236,-169 192,-169"/> +<text text-anchor="start" x="195" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- Loop7->If12 --> +<g id="edge3" class="edge"> +<title>Loop7->If12:p0</title> +<path fill="none" stroke="red" d="M142.11,-336.15C145.11,-334.74 148.12,-333.34 151,-332 186,-315.76 209.13,-328.45 230,-296 238.66,-282.54 239,-273.23 230,-260 205.55,-224.03 163.45,-259.97 139,-224 125.26,-203.78 121.98,-186.54 139,-169 146.02,-161.76 158.99,-157.07 166.4,-159.61"/> +<polygon fill="red" stroke="red" points="163.54,-161.62 172,-168 169.36,-157.74 163.54,-161.62"/> +<text text-anchor="middle" x="145.11" y="-329.75" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_arg9 --> +<g id="node5" class="node"> +<title>Phi_arg9</title> +<ellipse fill="lightyellow" stroke="black" cx="277" cy="-350" rx="37.09" ry="18"/> +<text text-anchor="middle" x="277" y="-346.3" font-family="Times,serif" font-size="14.00">φ_arg</text> +</g> +<!-- Phi_arg9->Start2 --> +<g id="edge5" class="edge"> +<title>Phi_arg9->Start2:p1</title> +<path fill="none" stroke="black" d="M273.71,-368.06C269.21,-386.44 259.37,-414.03 239.96,-421.23"/> +<polygon fill="black" stroke="black" points="239.23,-417.81 230,-423 240.46,-424.7 239.23,-417.81"/> +<text text-anchor="middle" x="270.71" y="-370.66" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_arg9->Loop7 --> +<g id="edge4" class="edge"> +<title>Phi_arg9->Loop7</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M258.53,-334.01C248.46,-326.34 235.34,-317.98 222,-314 195.59,-306.13 186,-304.88 160,-314 152.86,-316.5 146.01,-320.67 139.91,-325.3"/> +<polygon fill="black" stroke="black" points="137.55,-322.71 132.08,-331.78 142.01,-328.1 137.55,-322.71"/> +<text text-anchor="middle" x="255.53" y="-327.61" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Add18 --> +<g id="node10" class="node"> +<title>Add18</title> +<ellipse fill="none" stroke="black" cx="66" cy="-192" rx="27" ry="18"/> +<text text-anchor="middle" x="66" y="-188.3" font-family="Times,serif" font-size="14.00">+</text> +</g> +<!-- Phi_arg9->Add18 --> +<g id="edge6" class="edge"> +<title>Phi_arg9->Add18</title> +<path fill="none" stroke="black" d="M249.01,-337.87C243.12,-335.75 236.9,-333.68 231,-332 153.36,-309.94 104.1,-358.47 53,-296 35.4,-274.49 43.72,-241.53 53.12,-218.71"/> +<polygon fill="black" stroke="black" points="56.43,-219.88 57.28,-209.32 50.03,-217.04 56.43,-219.88"/> +<text text-anchor="middle" x="246.01" y="-331.47" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- LT10 --> +<g id="node6" class="node"> +<title>LT10</title> +<ellipse fill="none" stroke="black" cx="194" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="194" y="-274.3" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT10->Phi_arg9 --> +<g id="edge7" class="edge"> +<title>LT10->Phi_arg9</title> +<path fill="none" stroke="black" d="M210.4,-292.83C222.06,-302.66 237.95,-316.07 251.29,-327.31"/> +<polygon fill="black" stroke="black" points="249.3,-330.22 259.2,-333.99 253.81,-324.86 249.3,-330.22"/> +<text text-anchor="middle" x="207.4" y="-295.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_11 --> +<g id="node7" class="node"> +<title>Con_11</title> +<ellipse fill="none" stroke="black" cx="191" cy="-350" rx="30.59" ry="18"/> +<text text-anchor="middle" x="191" y="-346.3" font-family="Times,serif" font-size="14.00">#10</text> +</g> +<!-- LT10->Con_11 --> +<g id="edge8" class="edge"> +<title>LT10->Con_11</title> +<path fill="none" stroke="black" d="M193.26,-296.3C192.93,-304.02 192.53,-313.29 192.16,-321.89"/> +<polygon fill="black" stroke="black" points="188.66,-321.75 191.73,-331.9 195.66,-322.05 188.66,-321.75"/> +<text text-anchor="middle" x="190.26" y="-298.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If12->Loop7 --> +<g id="edge9" class="edge"> +<title>If12->Loop7</title> +<path fill="none" stroke="red" d="M176.87,-224.21C170.8,-235.44 164,-248.24 158,-260 147.34,-280.9 135.81,-304.85 127.37,-322.64"/> +<polygon fill="red" stroke="red" points="124.14,-321.29 123.03,-331.83 130.46,-324.29 124.14,-321.29"/> +<text text-anchor="middle" x="173.87" y="-226.81" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If12->LT10 --> +<g id="edge10" class="edge"> +<title>If12->LT10</title> +<path fill="none" stroke="black" d="M194,-224.28C194,-232.58 194,-241.48 194,-249.56"/> +<polygon fill="black" stroke="black" points="190.5,-249.6 194,-259.6 197.5,-249.6 190.5,-249.6"/> +<text text-anchor="middle" x="191" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Add16 --> +<g id="node9" class="node"> +<title>Add16</title> +<ellipse fill="none" stroke="black" cx="89" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="89" y="-274.3" font-family="Times,serif" font-size="14.00">+</text> +</g> +<!-- Add16->Con_6 --> +<g id="edge11" class="edge"> +<title>Add16->Con_6</title> +<path fill="none" stroke="black" d="M78.56,-294.88C72.8,-303.66 65.52,-314.74 59.04,-324.58"/> +<polygon fill="black" stroke="black" points="55.96,-322.9 53.4,-333.18 61.81,-326.74 55.96,-322.9"/> +<text text-anchor="middle" x="75.56" y="-297.48" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Add16->Phi_arg9 --> +<g id="edge12" class="edge"> +<title>Add16->Phi_arg9</title> +<path fill="none" stroke="black" d="M112.05,-287.58C143.64,-299.35 200.8,-320.63 238.83,-334.79"/> +<polygon fill="black" stroke="black" points="237.94,-338.19 248.54,-338.4 240.39,-331.63 237.94,-338.19"/> +<text text-anchor="middle" x="115.05" y="-290.18" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Add18->Con_6 --> +<g id="edge14" class="edge"> +<title>Add18->Con_6</title> +<path fill="none" stroke="black" d="M62.31,-210.01C59.48,-223.51 55.62,-242.9 53,-260 49.85,-280.62 47.25,-304.07 45.5,-321.72"/> +<polygon fill="black" stroke="black" points="41.99,-321.59 44.51,-331.88 48.96,-322.27 41.99,-321.59"/> +<text text-anchor="middle" x="59.31" y="-212.61" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Add18->Add16 --> +<g id="edge13" class="edge"> +<title>Add18->Add16</title> +<path fill="none" stroke="black" d="M70.65,-210C73.8,-221.5 78.03,-236.93 81.63,-250.08"/> +<polygon fill="black" stroke="black" points="78.34,-251.31 84.35,-260.03 85.09,-249.46 78.34,-251.31"/> +<text text-anchor="middle" x="67.65" y="-212.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return21->Phi_arg9 --> +<g id="edge16" class="edge"> +<title>Return21->Phi_arg9</title> +<path fill="none" stroke="black" d="M249.23,-124.03C252.22,-134.35 255.79,-147.83 258,-160 268.31,-216.9 273.41,-284.53 275.6,-321.49"/> +<polygon fill="black" stroke="black" points="272.13,-322.15 276.19,-331.93 279.12,-321.75 272.13,-322.15"/> +<text text-anchor="middle" x="246.23" y="-126.63" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return21->If12 --> +<g id="edge15" class="edge"> +<title>Return21->If12:p1</title> +<path fill="none" stroke="red" d="M230.4,-124.06C224.14,-133.27 217.55,-145.31 215.05,-157.87"/> +<polygon fill="red" stroke="red" points="211.55,-157.69 214,-168 218.51,-158.41 211.55,-157.69"/> +<text text-anchor="middle" x="227.4" y="-126.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/09-graph6.gv b/docs/chapter09/09-graph6.gv new file mode 100644 index 00000000..56dc6ada --- /dev/null +++ b/docs/chapter09/09-graph6.gv @@ -0,0 +1,60 @@ +digraph chapter09 { +/* +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; + +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Start</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Loop7 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg9 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT10 [ label="<" ]; + Con_11 [ label="#10" ]; + If12 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">If</TD></TR> + <TR><TD> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> + <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Con_24 [ label="#2" ]; + Add25 [ label="+" ]; + { rank=same; Loop7;Phi_arg9;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return21[taillabel=0 color=red]; + Loop7 -> Start2:p0[taillabel=1 color=red]; + Loop7 -> If12:p0[taillabel=2 color=red constraint=false]; + Phi_arg9 -> Loop7 [style=dotted taillabel=0]; + Phi_arg9 -> Start2:p1[taillabel=1]; + Phi_arg9 -> Add25[taillabel=2 constraint=false]; + LT10 -> Phi_arg9[taillabel=1]; + LT10 -> Con_11[taillabel=2]; + If12 -> Loop7[taillabel=0 color=red]; + If12 -> LT10[taillabel=1]; + Return21 -> If12:p1[taillabel=0 color=red]; + Return21 -> Phi_arg9[taillabel=1]; + Add25 -> Phi_arg9[taillabel=1]; + Add25 -> Con_24[taillabel=2]; +} diff --git a/docs/chapter09/09-graph6.svg b/docs/chapter09/09-graph6.svg new file mode 100644 index 00000000..36c70099 --- /dev/null +++ b/docs/chapter09/09-graph6.svg @@ -0,0 +1,184 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.43.0 (0) + --> +<!-- Title: chapter09 Pages: 1 --> +<svg width="338pt" height="492pt" + viewBox="0.00 0.00 338.00 492.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 488)"> +<title>chapter09</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-488 334,-488 334,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-476 322,-476 322,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="149,-439 149,-464 230,-464 230,-439 149,-439"/> +<polygon fill="none" stroke="black" points="149,-439 149,-464 230,-464 230,-439 149,-439"/> +<text text-anchor="start" x="171" y="-447.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="149,-408 149,-439 230,-439 230,-408 149,-408"/> +<polygon fill="yellow" stroke="transparent" points="154,-413 154,-434 194,-434 194,-413 154,-413"/> +<polygon fill="none" stroke="black" points="154,-413 154,-434 194,-434 194,-413 154,-413"/> +<text text-anchor="start" x="157" y="-419.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="194,-413 194,-434 225,-434 225,-413 194,-413"/> +<text text-anchor="start" x="197" y="-419.8" font-family="Times,serif" font-size="14.00">arg</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="157,-52 103,-52 103,-16 157,-16 157,-52"/> +<text text-anchor="middle" x="130" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return21 --> +<g id="node8" class="node"> +<title>Return21</title> +<polygon fill="yellow" stroke="black" points="163,-124 97,-124 97,-88 163,-88 163,-124"/> +<text text-anchor="middle" x="130" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return21 --> +<g id="edge1" class="edge"> +<title>Stop3->Return21</title> +<path fill="none" stroke="red" d="M130,-52.3C130,-60.02 130,-69.29 130,-77.89"/> +<polygon fill="red" stroke="red" points="126.5,-77.9 130,-87.9 133.5,-77.9 126.5,-77.9"/> +<text text-anchor="middle" x="127" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Loop7 --> +<g id="node3" class="node"> +<title>Loop7</title> +<polygon fill="yellow" stroke="black" points="314,-368 260,-368 260,-332 314,-332 314,-368"/> +<text text-anchor="middle" x="287" y="-346.3" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop7->Start2 --> +<g id="edge2" class="edge"> +<title>Loop7->Start2:p0</title> +<path fill="none" stroke="red" d="M259.93,-364.13C234.34,-376.71 197.56,-395.24 181.73,-405.41"/> +<polygon fill="red" stroke="red" points="179.34,-402.85 174,-412 183.88,-408.18 179.34,-402.85"/> +<text text-anchor="middle" x="256.93" y="-357.73" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If12 --> +<g id="node7" class="node"> +<title>If12</title> +<polygon fill="yellow" stroke="transparent" points="36,-195 36,-220 130,-220 130,-195 36,-195"/> +<polygon fill="none" stroke="black" points="36,-195 36,-220 130,-220 130,-195 36,-195"/> +<text text-anchor="start" x="77.5" y="-203.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="36,-164 36,-195 130,-195 130,-164 36,-164"/> +<polygon fill="yellow" stroke="transparent" points="41,-169 41,-190 81,-190 81,-169 41,-169"/> +<polygon fill="none" stroke="black" points="41,-169 41,-190 81,-190 81,-169 41,-169"/> +<text text-anchor="start" x="44" y="-175.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="81,-169 81,-190 125,-190 125,-169 81,-169"/> +<polygon fill="none" stroke="black" points="81,-169 81,-190 125,-190 125,-169 81,-169"/> +<text text-anchor="start" x="84" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- Loop7->If12 --> +<g id="edge3" class="edge"> +<title>Loop7->If12:p0</title> +<path fill="none" stroke="red" d="M259.79,-335.23C256.86,-334.03 253.9,-332.92 251,-332 163.26,-304.1 109.06,-364.01 47,-296 22.33,-268.96 18.57,-203.69 31.46,-184.44"/> +<polygon fill="red" stroke="red" points="33.44,-187.32 40,-179 29.68,-181.42 33.44,-187.32"/> +<text text-anchor="middle" x="256.79" y="-328.83" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_arg9 --> +<g id="node4" class="node"> +<title>Phi_arg9</title> +<ellipse fill="lightyellow" stroke="black" cx="133" cy="-350" rx="37.09" ry="18"/> +<text text-anchor="middle" x="133" y="-346.3" font-family="Times,serif" font-size="14.00">φ_arg</text> +</g> +<!-- Phi_arg9->Start2 --> +<g id="edge5" class="edge"> +<title>Phi_arg9->Start2:p1</title> +<path fill="none" stroke="black" d="M162.55,-360.93C181.21,-369 202.84,-382.27 208.56,-401.87"/> +<polygon fill="black" stroke="black" points="205.12,-402.59 210,-412 212.05,-401.61 205.12,-402.59"/> +<text text-anchor="middle" x="159.55" y="-363.53" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_arg9->Loop7 --> +<g id="edge4" class="edge"> +<title>Phi_arg9->Loop7</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M151.47,-334.01C161.54,-326.34 174.66,-317.98 188,-314 211,-307.14 219.35,-306.06 242,-314 249.14,-316.5 255.99,-320.67 262.09,-325.3"/> +<polygon fill="black" stroke="black" points="259.99,-328.1 269.92,-331.78 264.45,-322.71 259.99,-328.1"/> +<text text-anchor="middle" x="148.47" y="-327.61" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Add25 --> +<g id="node10" class="node"> +<title>Add25</title> +<ellipse fill="none" stroke="black" cx="209" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="209" y="-274.3" font-family="Times,serif" font-size="14.00">+</text> +</g> +<!-- LT10 --> +<g id="node5" class="node"> +<title>LT10</title> +<ellipse fill="none" stroke="black" cx="83" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="83" y="-274.3" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT10->Phi_arg9 --> +<g id="edge7" class="edge"> +<title>LT10->Phi_arg9</title> +<path fill="none" stroke="black" d="M94.1,-294.54C100.33,-303.26 108.25,-314.35 115.31,-324.24"/> +<polygon fill="black" stroke="black" points="112.58,-326.43 121.24,-332.53 118.27,-322.36 112.58,-326.43"/> +<text text-anchor="middle" x="91.1" y="-297.14" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_11 --> +<g id="node6" class="node"> +<title>Con_11</title> +<ellipse fill="none" stroke="black" cx="47" cy="-350" rx="30.59" ry="18"/> +<text text-anchor="middle" x="47" y="-346.3" font-family="Times,serif" font-size="14.00">#10</text> +</g> +<!-- LT10->Con_11 --> +<g id="edge8" class="edge"> +<title>LT10->Con_11</title> +<path fill="none" stroke="black" d="M74.65,-295.24C70.35,-303.6 65.01,-313.98 60.17,-323.39"/> +<polygon fill="black" stroke="black" points="57.02,-321.87 55.55,-332.37 63.24,-325.07 57.02,-321.87"/> +<text text-anchor="middle" x="71.65" y="-297.84" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If12->Loop7 --> +<g id="edge9" class="edge"> +<title>If12->Loop7</title> +<path fill="none" stroke="red" d="M138.19,-204.98C172.47,-214.83 215.47,-231.87 245,-260 262.62,-276.78 273.63,-302.43 279.94,-321.89"/> +<polygon fill="red" stroke="red" points="276.66,-323.13 282.89,-331.7 283.36,-321.11 276.66,-323.13"/> +<text text-anchor="middle" x="141.19" y="-198.58" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If12->LT10 --> +<g id="edge10" class="edge"> +<title>If12->LT10</title> +<path fill="none" stroke="black" d="M83,-224.28C83,-232.58 83,-241.48 83,-249.56"/> +<polygon fill="black" stroke="black" points="79.5,-249.6 83,-259.6 86.5,-249.6 79.5,-249.6"/> +<text text-anchor="middle" x="80" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return21->Phi_arg9 --> +<g id="edge12" class="edge"> +<title>Return21->Phi_arg9</title> +<path fill="none" stroke="black" d="M137.07,-124.33C140.84,-134.53 145.09,-147.78 147,-160 155.96,-217.25 145.96,-285.18 138.86,-322"/> +<polygon fill="black" stroke="black" points="135.39,-321.46 136.86,-331.95 142.26,-322.84 135.39,-321.46"/> +<text text-anchor="middle" x="134.07" y="-126.93" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return21->If12 --> +<g id="edge11" class="edge"> +<title>Return21->If12:p1</title> +<path fill="none" stroke="red" d="M117.92,-124C112.25,-133.3 106.24,-145.44 103.95,-157.94"/> +<polygon fill="red" stroke="red" points="100.46,-157.71 103,-168 107.43,-158.38 100.46,-157.71"/> +<text text-anchor="middle" x="114.92" y="-126.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_24 --> +<g id="node9" class="node"> +<title>Con_24</title> +<ellipse fill="none" stroke="black" cx="215" cy="-350" rx="27" ry="18"/> +<text text-anchor="middle" x="215" y="-346.3" font-family="Times,serif" font-size="14.00">#2</text> +</g> +<!-- Add25->Phi_arg9 --> +<g id="edge13" class="edge"> +<title>Add25->Phi_arg9</title> +<path fill="none" stroke="black" d="M185.95,-300.23C176.82,-308.64 166.31,-318.32 157.07,-326.83"/> +<polygon fill="black" stroke="black" points="188.64,-302.51 193.62,-293.17 183.89,-297.37 188.64,-302.51"/> +<polygon fill="black" stroke="black" points="154.66,-324.29 149.68,-333.64 159.4,-329.44 154.66,-324.29"/> +<text text-anchor="middle" x="190.62" y="-295.77" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Add25->Con_24 --> +<g id="edge14" class="edge"> +<title>Add25->Con_24</title> +<path fill="none" stroke="black" d="M210.48,-296.3C211.14,-304.02 211.94,-313.29 212.68,-321.89"/> +<polygon fill="black" stroke="black" points="209.19,-322.23 213.53,-331.9 216.17,-321.63 209.19,-322.23"/> +<text text-anchor="middle" x="207.48" y="-298.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/docs/chapter09/Makefile b/docs/chapter09/Makefile new file mode 100644 index 00000000..12659b8f --- /dev/null +++ b/docs/chapter09/Makefile @@ -0,0 +1,12 @@ +GV := $(wildcard *.gv) +SVG := $(patsubst %.gv,%.svg,$(GV)) + +.PHONY: all +all: $(SVG) + +%.svg: %.gv + dot -Tsvg $< > $@ + +.PHONY: clean +clean: + @rm -f $(SVG) diff --git a/docs/chapter09/README.md b/docs/chapter09/README.md new file mode 100644 index 00000000..85ebd168 --- /dev/null +++ b/docs/chapter09/README.md @@ -0,0 +1,275 @@ +# Chapter 9: Global Value Numbering and Iterative Peepholes + +In this chapter: + +* We add Global Value Numbering (Common Subexpression Elimination) +* We add a post-parse iterate-peepholes-to-fixed-point optimization + +No language changes in this chapter, just adding optimizations. + +You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter09) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter08...linear-chapter09) it to the previous chapter. + +## Engineering Peepholes + +The goal for Ch 9 is to "finish out" the Peepholes. Not that no more peeps will +appear, on the contrary lots will - but this is the infrastructure to organize +them, engineer them safe and sane; to apply them until a fixed point is reached. + +Really the goal here is to be fearless about adding a new peephole; if you got +it wrong you'll assert pretty fast. If you got it right, that new peep will +just run alongside all the others and they will all mutually benefit each other. + + +## Global Value Numbering + +Global Value Numbering works via a classic hash table, hashing on a Node's +opcode and inputs. If two Nodes do the same function on the same inputs, they +get the same result - and can be shared. + +* We add a global static hash table `GVN` in Node. +* We add a `Node.equals` call. Two Nodes are equals if they have the same + opcode - same Java class - on the same inputs. +* We add a `hashCode` on the Node opcode (proxy: Node string name) and inputs. +* `ConstantNode` and a few others need some extra custom bits here: the + constant 17 is not the same as 99, but both are the same `ConstantNode` class + and label. The `eq` and `hash` calls check (or hash) these extra bits. + +Since we edit the graph a lot, Nodes can have their inputs change which changes +their hash which means they can't be found in the GVN table. E.g. we swap out +the `Add` for a `Con` in: + + (Mul (Add 4 8) x) // Fold the constant + (Mul 12 x) // The `Mul` changes hash and inputs + +The fix for this is to require Nodes NOT be in the `GVN` table when their edges +modify; they need some sort of "edge lock". If "edges are locked", we need to +remove the Node from `GVN` first, before modifying the edges. + +* We use the `_hash` field as both a cached hash code and as the edge lock. If + zero the Node is unlocked and NOT in `GVN`. If non-zero, the Node is + "edge-locked" and IS in `GVN`. +* The `hashCode` function uses the cached hash if available and disallows an + accidental zero hash. +* We add a `unlock()` call to remove a Node from `GVN` before any edge is + changed. This is inserted in front of all calls which hack Node edges. All + edge modify code paths already ensure the Node is re-peepholed, and during + this revisit the Node will get re-inserted in the `GVN` table. + +This new `GVN` table is used in `peepholeOpt()` (and `peephole()`) to look for common +subexpressions on every peephole'd Node. If the Node has a zero hash, we look +for a hit against some previous identical Node. If found, we use the prior +Node and dead-code-eliminate this one. If not found, we insert this Node +into `GVN` as the "golden instance". If a Node already has a non-zero hash +we know it IS the "golden instance" and do not need to look in `GVN`. + +This entire optimization takes about a dozen lines in `iter()`, plus the common +shared `equals` and `hashCode` (about 50 more lines counting comments). + +One of our invariants is that Types monotonically increase while running +peepholes. GVN can break this briefly: of the two equivalent Nodes one has +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. + + +## Post-Parse Iterative Peepholes + +The post-Parse `IterPeeps` iterates the peepholes to a fixed point - so +no more peepholes apply. This should be linear because peepholes rarely +(never?) increase code size. The graph should monotonically reduce in some +dimension, which is usually size. It might also reduce in e.g. number of +MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper +ones. + +The theoretical overall worklist is mindless just grabbing the next thing and +doing it. If the graph changes, put the neighbors on the worklist. Lather, +Rinse, Repeat until the worklist runs dry. + +The main issues we have to deal with: + +* Nodes have uses; replacing some set of Nodes with another requires more graph + reworking. Not rocket science, but it can be fiddly. Its helpful to have a + small set of graph munging utilities, and the strong invariant that the graph + is stable and correct between peepholes. In our case `Node.subsume` does + most of the munging, building on our prior stable Node utilities. + +* Changing a Node also changes the graph "neighborhood". The neigbors need to + be checked to see if THEY can also peephole, and so on. After any peephole + or graph update we put a Nodes uses and defs on the worklist. + +* Our strong invariant is that for all Nodes, either they are on the worklist + OR no peephole applies. This invariant is easy to check, although expensive. + Basically the normal "iterate peepholes to a fixed point" is linear, and this + check is linear at each peephole step... so quadratic overall. Its a useful + assert, but one we can disable once the overall algorithm is stable - and + then turn it back on again when some new set of peepholes is misbehaving. + The code for this is turned on in `IterPeeps.iterate` as `assert + progressOnList(stop);` + + +## Distant Neighbors + +For some peepholes the "neighborhood" is farther away than just the immediate +uses or defs, and for these we need a longer range plan. E.g. some peephole +inspects "this -> A -> B -> C" and will swap itself for "D"; however it fails +some test at "B". Should "B" ever update we want to revisit "this" and recheck +his peepholes. So we will need to record a dependence of "this" in "B". +Updating "B" throws the dependent list ("this") onto the "todo" list. + +An example of a distant neighbor check is in `AddNode`, where we check for +stacked constants: `(Add (Add x 2) 1)` and we'd like `(Add x 3)` instead. +Suppose we're doing this check and we find instead `(Add (Phi (Add x 2) self) +1)` The check naturally bails out at the `Phi` since its not an `Add`, but the +`(Phi y self)` only has one unique input, and will itself peephole to `y` +eventually. When it does, our stacked `Add` peephole can apply. So the +failing Add peephole calls `phi.addDep(Add)` and registers a dependency on the +`Phi`. If the `Phi` indeed later optimizes, we add its depencencies (e.g. the +`Add`) back on our `IterPeeps` worklist and retry the stacked Add peephole. + +The general rule is: +* **If a peephole pattern inspects a remote node, it must go on the dependency list!** +* `node.in(1).in(2)` becomes `node.in(1).in(2).addDep(node)` + + +In this chapter we trigger on following patterns + +* Some peeps interact with `Phi` inputs; this can be direct or indirect, + e.g. `(Add Add( (Phi x) 2 ) 1)`. The peep can progress if all of the `Phi`'s + inputs are `Constant`'s. Thus, we make the node a dependent on the `Phi`'s + input. We also make the node a dependent of the `Phi`'s region. See + `Node.allCons()` and `PhiNode.allCons()`. +* The pattern `(Add (Add x 1) 2)` adds the outer `Add` as dependency of `x` + because `x` becoming a constant would enable progress. See + `AddNode.idealize()` +* When computing a `Phi`'s type, if we observe that the associated region's + corresponding control is live, we add the `Phi` as a dependent of the + region's control input, because if that control goes dead, it would allow the + `Phi` to undergo peephole. See `PhiNode.compute()` and + `PhiNode.singleUniqueInput()`. + +Changes: + +* We add a `ArrayList<Node> _deps` field to Node; initially null. +* 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 + dependents onto the worklist. + + +## Other Concerns + +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 + up. + - Some peepholes naturally reduce the graph directly; while some keeps its + 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 + - "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 + immediately gets pulled again. I.e., you end up spinning in a loop repeating + the same peeps while slowly migrating a e.g. left-spine add-tree into a + right-spine add-tree. A psuedo-random pull uses randomization to defeat bad + peep patterns. + +* 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 + around loops will require another visit. It is easy to construct a case + requiring O(N) passes, each of cost O(N) and the algorithm quickly goes + quadratic. + + The `addDeps` solution avoids this quadratic cost, in exchange for some more + costs in writing peepholes. + + +# Examples + +## Common SubExpressions via GVN + +### Example 1 + +Here is a small example that illustrates how GVN enables finding common sub-expressions. + +```java +int x = arg + arg; +if(arg < 10) { + return arg + arg; +} +else { + x = x + 1; +} +return x; +``` + +Prior to GVN, this would result in following graph. Note that the `arg+arg` is translated to `arg*2`. + + + +Adding GVN, we get below. Note how the `arg*2` expression is now reused rather than appearing twice. + + + +### Example 2 + +Being able to reuse common expressions, enables additional optimizations. Here is a simple example: + +```java +return arg*arg-arg*arg; +``` + +Without GVN the peepholes cannot see that both sides of the subtraction have the same value. + + + +With GVN, the peepholes can do a better job: + + + +## Post Parse Iterative Optimizations + +The worklist based post parse optimization enables certain optimizations that are not available +during parsing. For example: + +```java +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; +``` + +While parsing the `while` loop it is not yet known that `step` is a constant and will not change. Therefore, +without the post parse optimization we get following: + + + +Enabling the post parse worklist based optimization yields below. Notice that now the graph shows `arg+2` inside the +loop body: + + diff --git a/src/main/java/com/seaofnodes/simple/GraphEvaluator.java b/src/main/java/com/seaofnodes/simple/GraphEvaluator.java index f5694a78..9fc51c8e 100644 --- a/src/main/java/com/seaofnodes/simple/GraphEvaluator.java +++ b/src/main/java/com/seaofnodes/simple/GraphEvaluator.java @@ -167,7 +167,7 @@ public class GraphEvaluator { 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"); + if (res.type== ResultType.TIMEOUT) throw new RuntimeException("Timeout"); return res.value; } diff --git a/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index d75e5bea..b4d64d4c 100644 --- a/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -20,15 +20,16 @@ public class GraphVisualizer { public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } public GraphVisualizer() { this(false); } - public String generateDotOutput(Parser parser) { + public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } + public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) { // Since the graph has cycles, we need to create a flat list of all the // nodes in the graph. - Collection<Node> all = findAll(parser); + Collection<Node> all = findAll(stop, scope); StringBuilder sb = new StringBuilder(); - sb.append("digraph chapter08 {\n"); + sb.append("digraph chapter09 {\n"); sb.append("/*\n"); - sb.append(parser.src()); + sb.append(stop._src); sb.append("\n*/\n"); // To keep the Scopes below the graph and pointing up into the graph we @@ -52,15 +53,19 @@ public class GraphVisualizer { nodes(sb, all); // Now the scopes, in a cluster no edges - for( ScopeNode scope : parser._xScopes ) - scope( sb, scope ); + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scope( sb, xscope ); // Walk the Node edges nodeEdges(sb, all); // Walk the active Scope edges - for( ScopeNode scope : parser._xScopes ) - scopeEdges( sb, scope ); + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scopeEdges( sb, xscope ); sb.append("}\n"); return sb.toString(); @@ -232,12 +237,13 @@ public class GraphVisualizer { /** * Finds all nodes in the graph. */ - private Collection<Node> findAll(Parser parser) { + private Collection<Node> findAll(Node stop, Node scope) { final HashMap<Integer, Node> all = new HashMap<>(); - for( Node n : Parser.START._outputs ) - walk(all, n); - for( Node n : parser._scope._inputs ) + for( Node n : stop._inputs ) walk(all, n); + if( scope != null ) + for( Node n : scope._inputs ) + walk(all, n); return all.values(); } diff --git a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/src/main/java/com/seaofnodes/simple/IRPrinter.java index 2a6dbefd..c06ac9d0 100644 --- a/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/src/main/java/com/seaofnodes/simple/IRPrinter.java @@ -48,14 +48,15 @@ public class IRPrinter { // First walk the CFG, then everything if( n.isCFG() ) { for( Node use : n._outputs ) - if( use.isCFG() && use.nOuts()>=1 && !(use._outputs.get(0) instanceof LoopNode) ) + if( use!=null && use.isCFG() && use.nOuts()>=1 && !(use._outputs.get(0) instanceof LoopNode) ) postOrd(use, rpos,visit,bfs); for( Node use : n._outputs ) - if( use.isCFG() ) + if( use!=null && use.isCFG() ) postOrd(use,rpos,visit,bfs); } for( Node use : n._outputs ) - postOrd(use, rpos,visit,bfs); + if( use != null ) + postOrd(use, rpos,visit,bfs); // Post-order rpos.add(n); } diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java new file mode 100644 index 00000000..6dafd95f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -0,0 +1,204 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Random; + +/** + * The IterOptim runs after parsing. It iterates the peepholes to a fixed point + * so that no more peepholes apply. This should be linear because peepholes rarely + * (never?) increase code size. The graph should monotonically reduce in some + * dimension, which is usually size. It might also reduce in e.g. number of + * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper + * ones. + * + * The theoretical overall worklist is mindless just grabbing the next thing and + * doing it. If the graph changes, put the neighbors on the worklist. Lather, + * Rinse, Repeat until the worklist runs dry. + * + * The main issues we have to deal with: + * + * <ul> + * <li>Nodes have uses; replacing some set of Nodes with another requires more graph + * reworking. Not rocket science, but it can be fiddly. Its helpful to have a + * small set of graph munging utilities, and the strong invariant that the graph + * is stable and correct between peepholes. In our case `Node.subsume` does + * most of the munging, building on our prior stable Node utilities.</li> + * + * <li>Changing a Node also changes the graph "neighborhood". The neigbors need to + * be checked to see if THEY can also peephole, and so on. After any peephole + * or graph update we put a Nodes uses and defs on the worklist.</li> + * + * <li>Our strong invariant is that for all Nodes, either they are on the worklist + * OR no peephole applies. This invariant is easy to check, although expensive. + * Basically the normal "iterate peepholes to a fixed point" is linear, and this + * check is linear at each peephole step... so quadratic overall. Its a useful + * assert, but one we can disable once the overall algorithm is stable - and + * then turn it back on again when some new set of peepholes is misbehaving. + * The code for this is turned on in `IterOptim.iterate` as `assert + * progressOnList(stop);`</li> + * </ul> + */ +public abstract class IterPeeps { + + private static final WorkList<Node> WORK = new WorkList<>(); + + public static <N extends Node> N add( N n ) { + return (N)WORK.push(n); + } + + public static void addAll( ArrayList<Node> ary ) { + WORK.addAll(ary); + } + + /** + * Iterate peepholes to a fixed point + */ + public static StopNode iterate(StopNode stop, boolean show) { + assert progressOnList(stop); + int cnt=0; + + Node n; + while( (n=WORK.pop()) != null ) { + if( n.isDead() ) continue; + cnt++; // Useful for debugging, searching which peephole broke things + Node x = n.peepholeOpt(); + if( x != null ) { + if( x.isDead() ) continue; + // peepholeOpt can return brand-new nodes, needing an initial type set + if( x._type==null ) x.setType(x.compute()); + // Changes require neighbors onto the worklist + if( x != n || !(x instanceof ConstantNode) ) { + // All outputs of n (changing node) not x (prior existing node). + for( Node z : n._outputs ) WORK.push(z); + // Everybody gets a free "go again" in case they didn't get + // made in their final form. + WORK.push(x); + // If the result is not self, revisit all inputs (because + // there's a new user), and replace in the graph. + if( x != n ) { + for( Node z : n. _inputs ) WORK.push(z); + n.subsume(x); + } + } + // If there are distant neighbors, move to worklist + n.moveDepsToWorklist(); + assert progressOnList(stop); // Very expensive assert + } + } + + if( show ) + System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); + return stop; + } + + // Visit ALL nodes and confirm the invariant: + // Either you are on the WORK worklist OR running `iter()` makes no progress. + + // This invariant ensures that no progress is missed, i.e., when the + // worklist is empty we have indeed done all that can be done. To help + // with debugging, the {@code assert} is broken out in a place where it is easy to + // stop if a change is found. + + // Also, the normal usage of `iter()` may attempt peepholes with distance + // neighbors and these should fail, but will then try to add dependencies + // {@link #Node.addDep} which is a side effect in an assert. The {@link + // #midAssert} is used to stop this side effect. + private static boolean MID_ASSERT; + public static boolean midAssert() { return MID_ASSERT; } + private static boolean progressOnList(Node stop) { + MID_ASSERT = true; + int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; + Node changed = stop.walk( n -> { + if( WORK.on(n) ) return null; + Node m = n.peepholeOpt(); + if( m==null ) return null; + System.err.println("BREAK HERE FOR BUG"); + return m; + }); + Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop; + MID_ASSERT = false; + return changed==null; + } + + public static void reset() { + WORK.clear(); + } + + /** + * Classic WorkList, with a fast add/remove, dup removal, random pull. + * The Node's nid is used to check membership in the worklist. + */ + public static class WorkList<E extends Node> { + + private Node[] _es; + private int _len; + private final BitSet _on; // Bit set if Node._nid is on WorkList + private final Random _R; // For randomizing pull from the WorkList + private final long _seed; + + /* Useful stat - how many nodes are processed in the post parse iterative opt */ + private long _totalWork = 0; + + WorkList() { this(123); } + WorkList(long seed) { + _es = new Node[1]; + _len=0; + _on = new BitSet(); + _seed = seed; + _R = new Random(); + _R.setSeed(_seed); + } + + /** + * Pushes a Node on the WorkList, ensuring no duplicates + * If Node is null it will not be added. + */ + public E push( E x ) { + if( x==null ) return null; + int idx = x._nid; + if( !_on.get(idx) ) { + _on.set(idx); + if( _len==_es.length ) + _es = Arrays.copyOf(_es,_len<<1); + _es[_len++] = x; + _totalWork++; + } + return x; + } + + public void addAll( ArrayList<E> ary ) { + for( E n : ary ) + push(n); + } + + /** + * True if Node is on the WorkList + */ + boolean on( E x ) { return _on.get(x._nid); } + + /** + * Removes a random Node from the WorkList; null if WorkList is empty + */ + E pop() { + if( _len == 0 ) return null; + int idx = _R.nextInt(_len); + E x = (E)_es[idx]; + _es[idx] = _es[--_len]; // Compress array + _on.clear(x._nid); + return x; + } + + public void clear() { + _len = 0; + _on.clear(); + _R.setSeed(_seed); + _totalWork = 0; + } + } +} diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 69796828..aabdf63a 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -69,11 +69,12 @@ public class Parser { public Parser(String source, TypeInteger arg) { Node.reset(); + IterPeeps.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; START = new StartNode(new Type[]{ Type.CONTROL, arg }); - STOP = new StopNode(); + STOP = new StopNode(source); } public Parser(String source) { @@ -108,7 +109,6 @@ public class Parser { return STOP; } - /** * Parses a block * @@ -185,11 +185,10 @@ public class Parser { // Parse predicate var pred = require(parseExpression(), ")"); // IfNode takes current control and predicate - IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).keep().peephole(); + Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new ProjNode(ifNode, 0, "True" ).peephole(); - ifNode.unkeep(); - Node ifF = new ProjNode(ifNode, 1, "False").peephole(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole(); // Clone the body Scope to create the break/exit Scope which accounts for any // side effects in the predicate. The break/exit Scope will be the final @@ -203,7 +202,7 @@ public class Parser { // Parse the true side, which corresponds to loop body // Our current scope is the body Scope - ctrl(ifT); // set ctrl token to ifTrue projection + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection parseStatement(); // Parse loop body // Merge the loop bottom into other continue statements @@ -247,7 +246,7 @@ public class Parser { return cur; // toScope is either the break scope, or a scope that was created here assert toScope._scopes.size() <= _breakScope._scopes.size(); - toScope.mergeScopes(cur); + toScope.ctrl(toScope.mergeScopes(cur)); return toScope; } @@ -269,11 +268,10 @@ public class Parser { // Parse predicate var pred = require(parseExpression(), ")"); // IfNode takes current control and predicate - IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).<IfNode>keep().peephole(); + Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new ProjNode(ifNode, 0, "True" ).peephole(); - ifNode.unkeep(); - Node ifF = new ProjNode(ifNode, 1, "False").peephole(); + Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current int ndefs = _scope.nIns(); @@ -281,13 +279,13 @@ public class Parser { _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side - ctrl(ifT); // set ctrl token to ifTrue projection + 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); // Ctrl token is now set to ifFalse projection + ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection if (matchx("else")) { parseStatement(); fScope = _scope; @@ -324,8 +322,8 @@ public class Parser { * Dumps out the node graph * @return {@code null} */ - private Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(this)); + Node showGraph() { + System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); return null; } @@ -384,12 +382,23 @@ public class Parser { */ private Node parseComparison() { var lhs = parseAddition(); - if (match("==")) return new BoolNode.EQ(lhs, parseComparison()).peephole(); - if (match("!=")) return new NotNode(new BoolNode.EQ(lhs, parseComparison()).peephole()).peephole(); - if (match("<=")) return new BoolNode.LE(lhs, parseComparison()).peephole(); - if (match("<" )) return new BoolNode.LT(lhs, parseComparison()).peephole(); - if (match(">=")) return new BoolNode.LE(parseComparison(), lhs).peephole(); - if (match(">" )) return new BoolNode.LT(parseComparison(), lhs).peephole(); + while( true ) { + int idx=0; boolean negate=false; + // Test for any local nodes made, and "keep" lhs during peepholes + if( false ) ; + else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } + else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } + else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } + else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } + else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } + else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } + else break; + // Peepholes can fire, but lhs is already "hooked", kept alive + lhs.setDef(idx,parseAddition()); + lhs = lhs.peephole(); + if( negate ) // Extra negate for != + lhs = new NotNode(lhs).peephole(); + } return lhs; } @@ -402,9 +411,15 @@ public class Parser { * @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { - var lhs = parseMultiplication(); - if (match("+")) return new AddNode(lhs, parseAddition()).peephole(); - if (match("-")) return new SubNode(lhs, parseAddition()).peephole(); + Node lhs = parseMultiplication(); + while( true ) { + if( false ) ; + else if( match("+") ) lhs = new AddNode(lhs,null); + else if( match("-") ) lhs = new SubNode(lhs,null); + else break; + lhs.setDef(2,parseMultiplication()); + lhs = lhs.peephole(); + } return lhs; } @@ -418,8 +433,14 @@ public class Parser { */ private Node parseMultiplication() { var lhs = parseUnary(); - if (match("*")) return new MulNode(lhs, parseMultiplication()).peephole(); - if (match("/")) return new DivNode(lhs, parseMultiplication()).peephole(); + while( true ) { + if( false ) ; + else if( match("*") ) lhs = new MulNode(lhs,null); + else if( match("/") ) lhs = new DivNode(lhs,null); + else break; + lhs.setDef(2,parseUnary()); + lhs = lhs.peephole(); + } return lhs; } diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java index 84a368fd..4e30bb55 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -25,21 +25,16 @@ public class AddNode extends Node { in(2)._type instanceof TypeInteger i1 ) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(i0.value()+i1.value()); - return i0.meet(i1); } - return Type.BOTTOM; + return in(1)._type.meet(in(2)._type); } @Override public Node idealize () { Node lhs = in(1); Node rhs = in(2); - Type t1 = lhs._type; Type t2 = rhs._type; - // Already handled by peephole constant folding - assert !(t1.isConstant() && t2.isConstant()); - // Add of 0. We do not check for (0+x) because this will already // canonicalize to (x+0) if( t2 instanceof TypeInteger i && i.value()==0 ) @@ -55,6 +50,10 @@ public class AddNode extends Node { if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) return swap12(); + // x+(-y) becomes x-y + if( rhs instanceof MinusNode minus ) + return new SubNode(lhs,minus.in(1)); + // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) // Do we have x + (y + z) ? @@ -66,7 +65,7 @@ public class AddNode extends Node { // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) if( !(lhs instanceof AddNode) ) // Rotate; look for (add (phi cons) con/(phi cons)) - return spline_cmp(lhs,rhs) ? swap12() : phiCon(this,true); + return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); // Now we only see (add add non) @@ -77,7 +76,11 @@ public class AddNode extends Node { // Do we have (x + con1) + con2? // Replace with (x + (con1+con2) which then fold the constants - if( lhs.in(2)._type.isConstant() && t2.isConstant() ) + // lhs.in(2) is con1 here + // If lhs.in(2) is not a constant, we add ourselves as a dependency + // because if it later became a constant then we could make this + // transformation. + if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); @@ -87,11 +90,11 @@ public class AddNode extends Node { Node phicon = phiCon(this,true); if( phicon!=null ) return phicon; - // Now we sort along the spline via rotates, to gather similar things together. + // Now we sort along the spine via rotates, to gather similar things together. // Do we rotate (x + y) + z // into (x + z) + y ? - if( spline_cmp(lhs.in(2),rhs) ) + if( spine_cmp(lhs.in(2),rhs,this) ) return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); return null; @@ -104,16 +107,16 @@ public class AddNode extends Node { Node lhs = op.in(1); Node rhs = op.in(2); // LHS is either a Phi of constants, or another op with Phi of constants - PhiNode lphi = pcon(lhs); + PhiNode lphi = pcon(lhs,op); if( rotate && lphi==null && lhs.nIns() > 2 ) { // Only valid to rotate constants if both are same associative ops if( lhs.getClass() != op.getClass() ) return null; - lphi = pcon(lhs.in(2)); // Will rotate with the Phi push + 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 con) && pcon(rhs)==null ) + if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) return null; // If both are Phis, must be same Region @@ -134,21 +137,31 @@ public class AddNode extends Node { return lhs==lphi ? phi : op.copy(lhs.in(1),phi); } - static PhiNode pcon(Node op) { - return op instanceof PhiNode phi && phi.allCons() ? phi : null; + /** + * Tests if the op is a phi and has all constant inputs. + * If not, returns null. + * If op is a phi, but its inputs are not all constants, then dep is added as + * a dependency to the phi's non-const input, because if later the input turn into a constant + * dep can make progress. + */ + static PhiNode pcon(Node op, Node dep) { + return op instanceof PhiNode phi && phi.allCons(dep) ? phi : null; } - // Compare two off-spline nodes and decide what order they should be in. + // 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. // Ties with in a category sort by node ID. // TRUE if swapping hi and lo. - static boolean spline_cmp( Node hi, Node lo ) { + static boolean spine_cmp( Node hi, Node lo, Node dep ) { if( lo._type.isConstant() ) return false; if( hi._type.isConstant() ) return true ; - if( lo instanceof PhiNode && lo.allCons() ) return false; - if( hi instanceof PhiNode && hi.allCons() ) return true ; + if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false; + if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false; + + if( lo instanceof PhiNode && lo.allCons(dep) ) return false; + if( hi instanceof PhiNode && hi.allCons(dep) ) return true ; if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 0c32d50d..d3fcec95 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -32,9 +32,8 @@ abstract public class BoolNode extends Node { in(2)._type instanceof TypeInteger i1 ) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); - return i0.meet(i1); } - return Type.BOTTOM; + return in(1)._type.meet(in(2)._type); } abstract boolean doOp(long lhs, long rhs); diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 13006bfb..9e45198c 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -8,7 +8,7 @@ import java.util.BitSet; /** * A Constant node represents a constant value. At present, the only constants * that we allow are integer literals; therefore Constants contain an integer - * value. As we add other types of constants, we will refactor how we represent + * value. As we add other types of constants, we will refactor how we represent * Constants. * <p> * Constants have no semantic inputs. However, we set Start as an input to @@ -25,7 +25,7 @@ public class ConstantNode extends Node { } @Override - public String label() { return ""+_con; } + public String label() { return "#"+_con; } @Override public String uniqueName() { return "Con_" + _nid; } @@ -42,4 +42,17 @@ public class ConstantNode extends Node { @Override public Node idealize() { return null; } + + @Override + boolean eq(Node n) { + ConstantNode con = (ConstantNode)n; // Contract + return _con==con._con; + } + + @Override + int hash() { return _con.hashCode(); } + + // Dead control is its own idom root + Node idom() { return null; } + } diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java index 7207fd48..08ce70b8 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -27,9 +27,8 @@ public class DivNode extends Node { return i1.value() == 0 ? TypeInteger.ZERO : TypeInteger.constant(i0.value()/i1.value()); - return i0.meet(i1); } - return Type.BOTTOM; + return in(1)._type.meet(in(2)._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 23a8fafc..460dce51 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -32,27 +32,30 @@ public class IfNode extends MultiNode { // If the If node is not reachable then neither is any following Proj if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) return TypeTuple.IF_NEITHER; + Node pred = pred(); + Type t = pred._type; + // High types mean NEITHER side is reachable. + // Wait until the type falls to decide which way to go. + if( t == Type.TOP || t == TypeInteger.TOP ) + return TypeTuple.IF_NEITHER; // If constant is 0 then false branch is reachable // Else true branch is reachable - if (pred()._type instanceof TypeInteger ti && ti.isConstant()) { - if (ti.value() == 0) return TypeTuple.IF_FALSE; - else return TypeTuple.IF_TRUE; - } - - // Hunt up the immediate dominator tree. If we find an identical if - // test on either the true or false branch, then this test matches. - for( Node dom = idom(), prior=this; dom!=null; prior=dom, dom = dom.idom() ) - if( dom instanceof IfNode iff && iff.pred()==pred() ) - return prior instanceof ProjNode proj - // Repeated test, dominated on one side. Test result is the same. - ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE) - : dom._type; // Repeated test not dominated on one side + if (t instanceof TypeInteger ti && ti.isConstant()) + return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; return TypeTuple.IF_BOTH; } @Override public Node idealize() { + // Hunt up the immediate dominator tree. If we find an identical if + // test on either the true or false branch, that side wins. + if( !pred()._type.isHighOrConst() ) + for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) { + setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); + return this; + } return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 3d944fd4..dd092c9e 100644 --- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -22,7 +22,7 @@ public class MinusNode extends Node { public Type compute() { if (in(1)._type instanceof TypeInteger i0) return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; - return Type.BOTTOM; + return TypeInteger.TOP.meet(in(1)._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index 2e701a4e..7e9f88a2 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -25,9 +25,8 @@ public class MulNode extends Node { in(2)._type instanceof TypeInteger i1) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(i0.value()*i1.value()); - return i0.meet(i1); } - return Type.BOTTOM; + return in(1)._type.meet(in(2)._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 7ba1aa19..2743bd19 100644 --- a/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -4,4 +4,11 @@ public abstract class MultiNode extends Node { public MultiNode(Node... inputs) { super(inputs); } + // Find a projection by index + ProjNode proj( int idx ) { + for( Node out : _outputs ) + if( out instanceof ProjNode prj && prj._idx==idx ) + return prj; + return null; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 2d6d5ad5..5eb9c076 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,10 +1,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IRPrinter; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import java.util.*; +import java.util.function.Function; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -63,6 +65,7 @@ public abstract class Node { * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. * */ private static int UNIQUE_ID = 1; + public static int UID() { return UNIQUE_ID; } protected Node(Node... inputs) { _nid = UNIQUE_ID++; // allocate unique dense ID @@ -105,7 +108,7 @@ public abstract class Node { // 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) { - if (visited.get(_nid)) + if (visited.get(_nid) && !(this instanceof ConstantNode) ) return sb.append(label()); visited.set(_nid); return isDead() @@ -131,7 +134,7 @@ public abstract class Node { sb.append(" "); sb.append(" [[ "); for( Node use : _outputs ) - sb.append("%4d ".formatted(use._nid)); + sb.append(use==null ? "____ " : "%4d ".formatted(use._nid)); int lim = 5 - Math.max(_inputs.size(),3); for( int i = _outputs.size(); i<lim; i++ ) sb.append(" "); @@ -154,6 +157,7 @@ public abstract class Node { * @return Input node or null */ public Node in(int i) { return _inputs.get(i); } + public Node out(int i) { return _outputs.get(i); } public int nIns() { return _inputs.size(); } @@ -179,9 +183,10 @@ public abstract class Node { * @param new_def the new definition * @return new_def for flow coding */ - Node setDef(int idx, Node new_def ) { + public Node setDef(int idx, Node new_def ) { + unlock(); Node old_def = in(idx); - if( old_def == new_def ) return this; // No change + if( old_def == new_def ) return new_def; // No change // If new def is not null, add the corresponding def->use edge // This needs to happen before removing the old node's def->use edge as // the new_def might get killed if the old node kills it recursively. @@ -198,13 +203,17 @@ public abstract class Node { // 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. - void delDef(int idx) { + // Phi, but not for every Node. If the def goes dead, it is recursively + // killed, which may include 'this' Node. + Node delDef(int idx) { + unlock(); Node old_def = in(idx); if( old_def != null && // If the old def exists, remove a def->use edge old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def + old_def.moveDepsToWorklist(); Utils.del(_inputs, idx); + return this; } /** @@ -215,6 +224,7 @@ public abstract class Node { * @return new_def for flow coding */ Node addDef(Node new_def) { + unlock(); // Add use->def edge _inputs.add(new_def); // If new def is not null, add the corresponding def->use edge @@ -237,6 +247,7 @@ public abstract class Node { // Shortcut for "popping" n nodes. A "pop" is basically a // setDef(last,null) followed by lowering the nIns() count. void popN(int n) { + unlock(); for( int i=0; i<n; i++ ) { Node old_def = _inputs.removeLast(); if( old_def != null && // If it exists and @@ -247,18 +258,26 @@ public abstract class Node { /** * 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 - * code elimination. This function is co-recursive with {@link #popN}. + * to null. This may recursively kill more Nodes, and is basically dead + * code elimination. */ public void kill( ) { + unlock(); assert isUnused(); // Has no uses, so it is dead - popN(nIns()); // Set all inputs to null, recursively killing unused Nodes _type=null; // Flag as dead + while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes + Node old_def = _inputs.removeLast(); + if( old_def != null ) { + IterPeeps.add(old_def);// Revisit neighbor because removed use + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + } + } assert isDead(); // Really dead now } // Mostly used for asserts and printing. - boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive @@ -268,10 +287,11 @@ public abstract class Node { // Replace self with nnn in the graph, making 'this' go dead - void subsume( Node nnn ) { + public void subsume( Node nnn ) { assert nnn!=this; while( nOuts() > 0 ) { Node n = _outputs.removeLast(); + n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); nnn.addUse(n); @@ -289,6 +309,19 @@ public abstract class Node { public static boolean _disablePeephole = false; /** + * Try to peephole at this node and return a better replacement Node. + * Always returns some not-null Node (often this). + */ + public final Node peephole( ) { + if (_disablePeephole) { + _type = compute(); + return this; // Peephole optimizations turned off + } + Node n = peepholeOpt(); + return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress + } + + /** * Try to peephole at this node and return a better replacement Node if * possible. We compute a {@link Type} and then check and replace: * <ul> @@ -301,29 +334,45 @@ public abstract class Node { * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing * math constants, remove algebraic identities and generally simplify the * code. </li> + * + * Unlike peephole above, this explicitly returns null for no-change, or not-null + * for a better replacement (which can be this). * </ul> */ - public final Node peephole( ) { + public final Node peepholeOpt( ) { + ITER_CNT++; // Compute initial or improved Type - Type type = _type = compute(); - - if (_disablePeephole) - return this; // Peephole optimizations turned off + Type old = setType(compute()); // Replace constant computations from non-constants with a constant node - if (!(this instanceof ConstantNode) && type.isConstant()) - return deadCodeElim(new ConstantNode(type).peephole()); - - // Future chapter: Global Value Numbering goes here + if (!(this instanceof ConstantNode) && _type.isHighOrConst() ) + return new ConstantNode(_type).peepholeOpt(); + + // Global Value Numbering + if( _hash==0 ) { + Node n = GVN.get(this); // Will set _hash as a side effect + if( n==null ) + GVN.put(this,this); // Put in table now + else { + // Because of random worklist ordering, the two equal nodes + // might have different types. Because of monotonicity, both + // types are valid. To preserve monotonicity, the resulting + // shared Node has to have the best of both types. + n.setType(n._type.join(_type)); + _hash = 0; // Clear, since it never went in the table + return deadCodeElim(n);// Return previous; does Common Subexpression Elimination + } + } // Ask each node for a better replacement Node n = idealize(); if( n != null ) // Something changed - // Recursively optimize - return deadCodeElim(n.peephole()); + return n; // Report progress - return this; // No progress + if( old==_type ) ITER_NOP_CNT++; + return old==_type ? null : this; // Report progress } + public static int ITER_CNT, ITER_NOP_CNT; // m is the new Node, self is the old. // Return 'm', which may have zero uses but is alive nonetheless. @@ -353,13 +402,26 @@ public abstract class Node { * theory. Much later on, this will become important and allow us to do * many fancy complex optimizations trivially... because theory. * <p> - * compute() needs to be stand-alone, and cannot recursively call compute - * on its inputs programs are cyclic (have loops!) and this will just + * {@link #compute()} needs to be stand-alone, and cannot recursively call compute + * on its inputs, because programs are cyclic (have loops!) and this will just * infinitely recurse until stack overflow. Instead, compute typically * computes a new type from the {@link #_type} field of its inputs. */ public abstract Type compute(); + // Set the type. Assert monotonic progress. + // If changing, add users to worklist. + public Type setType(Type type) { + Type old = _type; + assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger + if( old == type ) return old; + _type = type; // Set _type late for easier assert debugging + IterPeeps.addAll(_outputs); + moveDepsToWorklist(); + return old; + } + + /** * This function rewrites the current Node into a more "idealized" form. * This is the bulk of our peephole rewrite rules, and we use this to @@ -399,32 +461,127 @@ public abstract class Node { * call peephole. * <p> * Since idealize calls peephole and peephole calls idealize, you must be - * careful that all idealizations are <em>monotonic</em>: all transforms remove - * some feature, so that the set of available transforms always shrinks. - * If you don't, you risk an infinite peephole loop! + * careful that all idealizations are <em>monotonic</em>: all transforms + * remove some feature, so that the set of available transforms always + * shrinks. If you don't, you risk an infinite peephole loop! * * @return Either a new or changed node, or null for no changes. */ public abstract Node idealize(); + // Some of the peephole rules get complex, and search further afield than + // just the nearest neighbor. These peepholes can fail the pattern match + // on a node some distance away, and if that node ever changes we should + // retry the peephole. Track a set of Nodes dependent on `this`, and + // revisit them if `this` changes. + ArrayList<Node> _deps; + + /** + * Add a node to the list o dependencies. Only add it if its not + * an input or output of this node, that is, it is at least one step + * away. The node being added must benefit from this node being peepholed. + */ + Node addDep( Node dep ) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( IterPeeps.midAssert() ) return this; + if( _deps==null ) _deps = new ArrayList<>(); + if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list + if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors + if( Utils.find(_outputs,dep)!= -1 ) return this; + _deps.add(dep); + return this; + } + + // Move the dependents onto a worklist, and clear for future dependents. + public void moveDepsToWorklist( ) { + if( _deps==null ) return; + IterPeeps.addAll(_deps); + _deps.clear(); + } + + + // Global Value Numbering. Hash over opcode and inputs; hits in this table + // are structurally equal. + public static final HashMap<Node,Node> GVN = new HashMap<>(); + + // Two nodes are equal if they have the same inputs and the same "opcode" + // which means the same Java class, plus same internal parts. + @Override public final boolean equals( Object o ) { + if( o==this ) return true; + if( o.getClass() != getClass() ) return false; + Node n = (Node)o; + int len = _inputs.size(); + if( len != n._inputs.size() ) return false; + for( int i=0; i<len; i++ ) + if( in(i) != n.in(i) ) + return false; + return eq(n); + } + // Subclasses add extra checks (such as ConstantNodes have same constant), + // and can assume "this!=n" and has the same Java class. + boolean eq( Node n ) { return true; } + + + // Cached hash. If zero, then not computed AND this Node is NOT in the GVN + // table - and can have its edges hacked (which will change his hash + // anyway). If Non-Zero then this Node is IN the GVN table, or is being + // probed to see if it can be inserted. His edges are "locked", because + // hacking his edges will change his hash. + int _hash; + + // If the _hash is set, then the Node is in the GVN table; remove it. + void unlock() { + if( _hash==0 ) return; + Node old = GVN.remove(this); // Pull from table + assert old==this; + _hash=0; // Out of table now + } + + + // Hash of opcode and inputs + @Override public final int hashCode() { + if( _hash != 0 ) return _hash; + int hash = hash(); + for( Node n : _inputs ) + if( n != null ) + hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid; + if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing + return (_hash=hash); + } + // Subclasses add extra hash info (such as ConstantNodes constant) + int hash() { return 0; } + + // ------------------------------------------------------------------------ // Peephole utilities // Swap inputs without letting either input go dead during the swap. Node swap12() { + unlock(); // Hash is order dependent Node tmp = in(1); _inputs.set(1,in(2)); _inputs.set(2,tmp); return this; } - // does this node contain all constants? - // Ignores in(0), as is usually control. - boolean allCons() { + /** + * Does this node contain all constants? + * Ignores in(0), as is usually control. + * In an input is not a constant, we add dep as + * a dependency to it, because dep can make progress + * if the input becomes a constant later. + * It is sufficient for one of the non-const + * inputs to have the dependency so we don't bother + * checking the rest. + */ + boolean allCons(Node dep) { for( int i=1; i<nIns(); i++ ) - if( !(in(i)._type.isConstant()) ) + if( !(in(i)._type.isConstant()) ) { + in(i).addDep(dep); // If in(i) becomes a constant later, will trigger some peephole return false; + } return true; } @@ -448,25 +605,35 @@ public abstract class Node { public static void reset() { UNIQUE_ID = 1; _disablePeephole=false; + GVN.clear(); + ITER_CNT = ITER_NOP_CNT = 0; + } + + + // Utility to walk the entire graph applying a function; return the first + // not-null result. + private static final BitSet WVISIT = new BitSet(); + final public <E> E walk( Function<Node,E> pred ) { + assert WVISIT.isEmpty(); + E rez = _walk(pred); + 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); + E x = pred.apply(this); + if( x != null ) return x; + for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; + for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; + return null; } /** * Debugging utility to find a Node by index */ - public Node find(int nid) { return _find(new BitSet(),nid); } - private Node _find(BitSet visit, int nid) { - if( _nid==nid ) return this; - if( visit.get(_nid) ) return null; - visit.set(_nid); - for( Node def : _inputs ) - if( def!=null ) { - Node rez = def._find(visit,nid); - if( rez != null ) return rez; - } - for( Node use : _outputs ) { - Node rez = use._find(visit,nid); - if( rez != null ) return rez; - } - return null; + public Node find(int nid) { + return walk( n -> n._nid==nid ? n : null ); } } diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index 39df410d..7a520fec 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -22,7 +22,7 @@ public class NotNode extends Node { public Type compute() { if( in(1)._type instanceof TypeInteger i0 ) return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; - return Type.BOTTOM; + return TypeInteger.TOP.meet(in(1)._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index c7b5b357..ec4c6ad3 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,7 +16,7 @@ public class PhiNode extends Node { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( region().inProgress() ) + if( !(region() instanceof RegionNode r) || r.inProgress() ) sb.append("Z"); sb.append("Phi("); for( Node in : _inputs ) { @@ -29,23 +29,29 @@ public class PhiNode extends Node { return sb; } - RegionNode region() { return (RegionNode)in(0); } + Node region() { return in(0); } @Override public boolean isMultiTail() { return true; } @Override public Type compute() { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - return Type.BOTTOM; + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? Type.TOP : _type; + if( r.inProgress() ) return Type.BOTTOM; Type t = Type.TOP; for (int i = 1; i < nIns(); i++) - t = t.meet(in(i)._type); + // If the region's control input is live, add this as a dependency + // to the control because we can be peeped should it become dead. + if( r.in(i).addDep(this)._type != Type.XCONTROL && in(i) != this ) + t = t.meet(in(i)._type); return t; } @Override public Node idealize() { - if( !(region() instanceof RegionNode r ) || r.inProgress() ) - return null; + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || r.nIns()<=1 ) + return null; // Input is in-progress // If we have only a single unique input, become it. Node live = singleUniqueInput(); @@ -84,11 +90,13 @@ public class PhiNode extends Node { * If only single unique input, return it */ private Node singleUniqueInput() { + if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL ) + return null; // Dead entry loops just ignore and let the loop collapse Node live = null; for( int i=1; i<nIns(); i++ ) { - if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL ) - return null; // Dead entry loops just ignore and let the loop collapse - if( region().in(i)._type != Type.XCONTROL && in(i) != this ) + // If the region's control input is live, add this as a dependency + // to the control because we can be peeped should it become dead. + if( region().in(i).addDep(this)._type != Type.XCONTROL && in(i) != this ) if( live == null || live == in(i) ) live = in(i); else return null; } @@ -96,9 +104,22 @@ public class PhiNode extends Node { } @Override - boolean allCons() { - if( !(region() instanceof RegionNode r) || r.inProgress() ) - return false; - return super.allCons(); + boolean allCons(Node dep) { + if( !(region() instanceof RegionNode r) ) return false; + // When the region completes (is no longer in progress) the Phi can + // become a "all constants" Phi, and the "dep" might make progress. + addDep(dep); + if( r.inProgress() ) return false; + return super.allCons(dep); + } + + // True if last input is null + public final boolean inProgress() { + return in(nIns()-1) == null; + } + + // Never equal if inProgress + @Override boolean eq( Node n ) { + return !inProgress(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 3c3f73b8..0ed9441d 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -7,7 +7,7 @@ import java.util.BitSet; public class ProjNode extends Node { - // Which slice of the incoming multi-part value + // Which slice of the incoming multipart value public final int _idx; // Debugging label @@ -32,7 +32,7 @@ public class ProjNode extends Node { @Override public Type compute() { - Type t = ctrl()._type; + Type t = in(0)._type; return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; } @@ -42,4 +42,10 @@ public class ProjNode extends Node { return ctrl().in(0); // We become our input control return null; } + + @Override + boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } + + @Override + int hash() { return _idx; } } diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 453d028f..c949c2b2 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -1,9 +1,11 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import java.util.Collection; +import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; public class RegionNode extends Node { @@ -34,22 +36,31 @@ public class RegionNode extends Node { public Node idealize() { if( inProgress() ) return null; int path = findDeadInput(); - if( path != 0 ) { - for( Node phi : _outputs ) - if( phi instanceof PhiNode ) - phi.delDef(path); - delDef(path); - - // If down to a single input, become that input - but also make all - // Phis an identity on *their* single input. - if( nIns()==2 ) { - for( Node phi : _outputs ) - if( phi instanceof PhiNode ) - // Currently does not happen, because no loops - throw Utils.TODO(); - return in(1); + if( path != 0 && + // Do not delete the entry path of a loop (ok to remove the back + // edge and make the loop a single-entry Region which folds away + // the Loop). Folding the entry path confused the loop structure, + // moving the backedge to the entry point. + !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { + // Cannot use the obvious output iterator here, because a Phi + // deleting an input might recursively delete *itself*. This + // shuffles the output array, and we might miss iterating an + // unrelated Phi. So on rare occasions we repeat the loop to get + // all the Phis. + int nouts = 0; + while( nouts != nOuts() ) { + nouts = nOuts(); + for( int i=0; i<nOuts(); i++ ) + if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) + phi.delDef(path); } - return this; + _idom = null; // Clear idom cache + return isDead() ? new ConstantNode(Type.XCONTROL) : delDef(path); + } + // If down to a single input, become that input + if( nIns()==2 && !hasPhi() ) { + _idom = null; // Clear idom cache + return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own } return null; } @@ -61,11 +72,22 @@ public class RegionNode extends Node { return 0; // All inputs alive } + private boolean hasPhi() { + for( Node phi : _outputs ) + if( phi instanceof PhiNode ) + return true; + return false; + } + // Immediate dominator of Region is a little more complicated. private Node _idom; // Immediate dominator cache @Override Node idom() { - if( _idom != null ) return _idom; // Return cached copy - if( nIns()!=3 ) return null; // Fails for anything other than 2-inputs + if( _idom != null ) { + if( _idom.isDead() ) _idom=null; + else return _idom; // Return cached copy + } + if( nIns()==2 ) return in(1); // 1-input is that one input + if( nIns()!=3 ) return null; // Fails for anything other than 2-inputs // Walk the LHS & RHS idom trees in parallel until they match, or either fails Node lhs = in(1).idom(); Node rhs = in(2).idom(); @@ -77,11 +99,17 @@ public class RegionNode extends Node { } if( lhs==null ) return null; _idepth = lhs._idepth+1; - return (_idom=lhs); + if( !IterPeeps.midAssert() ) _idom=lhs; + return lhs; } // True if last input is null public final boolean inProgress() { - return in(nIns()-1) == null; + return nIns()>1 && in(nIns()-1) == null; + } + + // Never equal if inProgress + @Override boolean eq( Node n ) { + return !inProgress(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 312058b9..922a3649 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -34,7 +34,7 @@ public class ReturnNode extends Node { @Override public Type compute() { - return new TypeTuple(ctrl()._type,expr()._type); + return TypeTuple.make(ctrl()._type,expr()._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index e5d03d93..3abea54d 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.Type; import java.util.*; @@ -212,6 +213,8 @@ public class ScopeNode extends Node { if( in(i) instanceof PhiNode phi ) { // Do an eager useless-phi removal Node in = phi.peephole(); + IterPeeps.addAll(phi._outputs); + phi.moveDepsToWorklist(); if( in != phi ) { phi.subsume(in); setDef(i,in); // Set the update back into Scope diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index 01f1485a..e399138b 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -18,7 +18,7 @@ public class StartNode extends MultiNode { public StartNode(Type[] args) { super(); - _args = new TypeTuple(args); + _args = TypeTuple.make(args); _type = _args; } diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 417790d9..602feffc 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -1,12 +1,17 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.IterPeeps; import java.util.BitSet; public class StopNode extends Node { - public StopNode(Node... inputs) { - super(inputs); + + public final String _src; + + public StopNode(String src) { + super(); + _src = src; } @Override @@ -50,4 +55,6 @@ public class StopNode extends Node { return addDef(node); } + public StopNode iterate( ) { return IterPeeps.iterate(this,false); } + public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ); } } diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java index af66b950..f661be14 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -29,9 +29,8 @@ public class SubNode extends Node { in(2)._type instanceof TypeInteger i1) { if (i0.isConstant() && i1.isConstant()) return TypeInteger.constant(i0.value()-i1.value()); - return i0.meet(i1); } - return Type.BOTTOM; + return in(1)._type.meet(in(2)._type); } @Override diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index 2f0e6171..d0265e83 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -1,5 +1,8 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.Utils; + +import java.util.HashMap; /** * These types are part of a Monotone Analysis Framework, @@ -18,6 +21,7 @@ package com.seaofnodes.simple.type; */ public class Type { + static final HashMap<Type,Type> INTERN = new HashMap<>(); // ---------------------------------------------------------- // Simple types are implemented fully here. "Simple" means: the code and @@ -36,15 +40,53 @@ public class Type { private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; protected Type(byte type) { _type = type; } - public static final Type BOTTOM = new Type( TBOT ); // ALL - public static final Type TOP = new Type( TTOP ); // ANY - public static final Type CONTROL = new Type( TCTRL ); // Ctrl - public static final Type XCONTROL = new Type( TXCTRL ); // ~Ctrl + public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL + public static final Type TOP = new Type( TTOP ).intern(); // ANY + public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl + public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl + + // Is high or on the lattice centerline. + public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } - public boolean isConstant() { return _type == TTOP || _type == TXCTRL; } + // Strict constant values, things on the lattice centerline. + // Excludes both high and low values + public boolean isConstant() { return false; } public StringBuilder _print(StringBuilder sb) {return is_simple() ? sb.append(STRS[_type]) : sb;} + // ---------------------------------------------------------- + + // Factory method which interns "this" + protected <T extends Type> T intern() { + T nnn = (T)INTERN.get(this); + if( nnn==null ) + INTERN.put(nnn=(T)this,this); + return nnn; + } + + private int _hash; // Hash cache; not-zero when set. + @Override + public final int hashCode() { + if( _hash!=0 ) return _hash; + _hash = hash(); + if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing + return _hash; + } + // Override in subclasses + int hash() { return _type; } + + @Override + public final boolean equals( Object o ) { + if( o==this ) return true; + if( !(o instanceof Type t)) return false; + if( _type != t._type ) return false; + return eq(t); + } + // Overridden in subclasses; subclass can assume "this!=t" and java classes are same + boolean eq(Type t) { return true; } + + + // ---------------------------------------------------------- public final Type meet(Type t) { // Shortcut for the self case if( t == this ) return this; @@ -53,7 +95,7 @@ public class Type { // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". if( is_simple() ) return this.xmeet(t ); if( t.is_simple() ) return t .xmeet(this); - return BOTTOM; // Mixing 2 unrelated types + return xmeet(t); // Mixing 2 unrelated types } // Compute meet right now. Overridden in subclasses. @@ -70,6 +112,28 @@ public class Type { return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; } + // 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. + public final Type join(Type t) { + if( this==t ) return this; + return dual().meet(t.dual()).dual(); + } + + public Type dual() { + return switch( _type ) { + case TBOT -> TOP; + case TTOP -> BOTTOM; + case TCTRL -> XCONTROL; + case TXCTRL -> CONTROL; + default -> throw Utils.TODO(); // Should not reach here + }; + } + + // ---------------------------------------------------------- @Override public final String toString() { return _print(new StringBuilder()).toString(); diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index b30e21d8..ba045603 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -5,59 +5,70 @@ package com.seaofnodes.simple.type; */ public class TypeInteger extends Type { - public final static TypeInteger TOP = new TypeInteger(false, 0); - public final static TypeInteger BOT = new TypeInteger(false, 1); - public final static TypeInteger ZERO= new TypeInteger(true, 0); + public final static TypeInteger TOP = make(false, 0); + public final static TypeInteger BOT = make(false, 1); + public final static TypeInteger ZERO= make(true , 0); - private final boolean _is_con; + public final boolean _is_con; /** * The constant value or - * if not constant then 0=bottom, 1=top. + * if not constant then 1=bottom, 0=top. */ - private final long _con; + public final long _con; - public TypeInteger(boolean is_con, long con) { + private TypeInteger(boolean is_con, long con) { super(TINT); _is_con = is_con; _con = con; } + public static TypeInteger make(boolean is_con, long con) { + return new TypeInteger(is_con,con).intern(); + } - public static TypeInteger constant(long con) { return new TypeInteger(true, con); } - - public boolean isTop() { return !_is_con && _con==0; } - public boolean isBot() { return !_is_con && _con==1; } + public static TypeInteger constant(long con) { return make(true, con); } @Override public StringBuilder _print(StringBuilder sb) { - if (isTop()) return sb.append("IntTop"); - if (isBot()) return sb.append("IntBot"); + if( this==TOP ) return sb.append("IntTop"); + if( this==BOT ) return sb.append("IntBot"); return sb.append(_con); } @Override + public boolean isHighOrConst() { return _is_con || _con==0; } + + @Override public boolean isConstant() { return _is_con; } public long value() { return _con; } @Override public Type xmeet(Type other) { - if( this==other ) return this; - if (!(other instanceof TypeInteger i)) return super.meet(other); + // Invariant from caller: 'this' != 'other' and same class (TypeInteger) + TypeInteger i = (TypeInteger)other; // Contract // BOT wins - if ( isBot() ) return this; - if ( i.isBot() ) return i ; + if ( this==BOT ) return this; + if ( i ==BOT ) return i ; // TOP loses - if ( i.isTop() ) return this; - if ( isTop() ) return i ; - assert isConstant() && i.isConstant(); - return _con==i._con ? this : TypeInteger.BOT; + if ( i ==TOP ) return this; + if ( this==TOP ) return i ; + // Since both are constants, and are never equals (contract) unequals + // constants fall to bottom + return BOT; } @Override - public boolean equals( Object o ) { - if( o==this ) return true; - if( !(o instanceof TypeInteger i) ) return false; + public Type dual() { + 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 + public boolean eq( Type t ) { + TypeInteger i = (TypeInteger)t; // Contract return _con==i._con && _is_con==i._is_con; } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 0f8b95bd..e82ea00b 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -4,14 +4,24 @@ public class TypeTuple extends Type { public final Type[] _types; - public TypeTuple(Type... _types) { - super(TTUPLE); - this._types = _types; - } + private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } + public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } @Override public Type xmeet(Type other) { - throw new UnsupportedOperationException("Meet on Tuple Type not yet implemented"); + TypeTuple tt = (TypeTuple)other; // contract from xmeet + assert _types.length == tt._types.length; + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].meet(tt._types[i]); + return make(ts); + } + + @Override public Type dual() { + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].dual(); + return make(ts); } @Override @@ -24,9 +34,27 @@ public class TypeTuple extends Type { return sb; } - public static final TypeTuple IF_BOTH = new TypeTuple(new Type[]{Type.CONTROL, Type.CONTROL}); - public static final TypeTuple IF_NEITHER = new TypeTuple(new Type[]{Type.XCONTROL,Type.XCONTROL}); - public static final TypeTuple IF_TRUE = new TypeTuple(new Type[]{Type.CONTROL, Type.XCONTROL}); - public static final TypeTuple IF_FALSE = new TypeTuple(new Type[]{Type.XCONTROL,Type.CONTROL}); + public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); + public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); + public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); + public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); + + @Override + int hash() { + int sum = 0; + for( Type type : _types ) sum ^= type.hashCode(); + return sum; + } + + @Override + boolean eq( Type t ) { + TypeTuple tt = (TypeTuple)t; // Contract + if( _types.length != tt._types.length ) return false; + for( int i=0; i<_types.length; i++ ) + if( _types[i]!=tt._types[i] ) + return false; + return true; + } + } diff --git a/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 47cee096..75ee0b70 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -13,7 +13,7 @@ public class Chapter02Test { Parser parser = new Parser("return 1+2*3+-5;"); Node._disablePeephole = true; // disable peephole so we can observe full graph StopNode ret = parser.parse(); - assertEquals("return (1+((2*3)+(-5)));", ret.print()); + assertEquals("return ((1+(2*3))+(-5));", ret.print()); Node._disablePeephole = false; } diff --git a/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/src/test/java/com/seaofnodes/simple/Chapter03Test.java index c76df903..a243beb1 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -40,7 +40,7 @@ public class Chapter03Test { @Test public void testVarDist() { - Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); "); + Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1);"); StopNode ret = parser.parse(); assertEquals("return 8;", ret.print()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 68d379a1..6ad3af15 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -21,7 +21,7 @@ else { } return a;"""); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString()); } @@ -36,7 +36,7 @@ if (arg == 1) { c = 4; } return c;""", TypeInteger.BOT); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return Phi(Region16,4,3);", ret.toString()); } @@ -64,7 +64,7 @@ if( arg==1 ) else b=a+1; return a+b;"""); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return ((arg*2)+Phi(Region20,2,3));", ret.toString()); } @@ -79,7 +79,7 @@ if( arg==1 ) else a=b+1; return a+b;"""); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return ((Phi(Region31,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); } @@ -99,7 +99,7 @@ else a=5; return a; """, TypeInteger.BOT); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region33,Phi(Region21,2,3),Phi(Region31,4,5));", stop.toString()); } @@ -129,7 +129,7 @@ if( arg==1 ) a=arg==3; } return a;"""); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return (arg==Phi(Region16,3,2));", ret.toString()); } @@ -169,6 +169,7 @@ return a;"""); } } + @Test public void testBadNum() { try { diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java index b858b83e..054e96e8 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -15,7 +15,7 @@ public class Chapter06Test { if( true ) return 2; return 1; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -29,7 +29,7 @@ if (arg) a = 2; return (arg < a) < 3; """); - StopNode stop = parser.parse(false); + StopNode stop = parser.parse().iterate(); assertEquals("return ((arg<Phi(Region12,2,1))<3);", stop.toString()); } @@ -44,7 +44,7 @@ else a=3; return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return 2;", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -64,7 +64,7 @@ if( a==2 ) else b=5; return b;""", TypeInteger.BOT); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region32,42,5);", stop.toString()); } @@ -83,8 +83,8 @@ if( arg==2 ) else b=5; return b;""", TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("return Phi(Region28,2,5);", stop.toString()); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region29,2,5);", stop.toString()); } @Test @@ -123,7 +123,7 @@ else a=5; return a; """, TypeInteger.constant(1)); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return 3;", stop.toString()); } @@ -143,8 +143,8 @@ else a=5; return a; """, TypeInteger.BOT); - StopNode stop = parser.parse(); - assertEquals("return Phi(Region36,3,Phi(Region34,4,5));", stop.toString()); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region37,3,Phi(Region35,4,5));", stop.toString()); } @Test @@ -163,7 +163,7 @@ else a=5; return a; """, TypeInteger.constant(1)); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return 3;", stop.toString()); } @@ -183,7 +183,7 @@ else a=5; return a; """, TypeInteger.constant(3)); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return 4;", stop.toString()); } @@ -199,8 +199,8 @@ if( arg ) { } return a+b; """); - StopNode ret = parser.parse(); - assertEquals("return Phi(Region22,4,1);", ret.toString()); + StopNode ret = parser.parse().iterate(); + assertEquals("return Phi(Region24,4,1);", ret.toString()); } @@ -216,7 +216,7 @@ if( arg ) { } return a+b; """, TypeInteger.constant(1)); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return 4;", ret.toString()); } @@ -232,7 +232,7 @@ if( arg ) { } return a+b; """, TypeInteger.constant(0)); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return 1;", ret.toString()); } @@ -250,8 +250,8 @@ if( arg ) { } return a+b+c; """); - StopNode ret = parser.parse(); - assertEquals("return (Phi(Region33,Phi(Region22,2,3),0)+Phi(Region,3,1));", ret.toString()); + StopNode ret = parser.parse().iterate(); + assertEquals("return (Phi(Region35,Phi(Region22,2,3),0)+Phi(Region,3,1));", ret.toString()); } @@ -269,7 +269,7 @@ if( arg ) { } return a+b+c; """, TypeInteger.constant(1)); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return 6;", ret.toString()); } @@ -287,7 +287,7 @@ if( arg ) { } return a+b+c; """, TypeInteger.constant(2)); - StopNode ret = parser.parse(); + StopNode ret = parser.parse().iterate(); assertEquals("return 5;", ret.toString()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java index d691672e..adf10471 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -36,7 +36,7 @@ if(arg){}else{ } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region22,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString()); } @@ -56,7 +56,7 @@ while(i < arg) { } return sum; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop8,0,Phi(Loop20,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @@ -93,7 +93,7 @@ while(a < 10) { } return b; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop8,2,(Phi(Region26,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -128,7 +128,7 @@ while(a < 10) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -156,7 +156,7 @@ int a = 1; while(arg) a = 2; return a; """); - StopNode stop = parser.parse(false); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop7,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -190,7 +190,7 @@ while(a < 10) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -226,7 +226,7 @@ while(a < 10) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 5d9d1545..cea6dd0f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -4,22 +4,12 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Assert; import org.junit.Test; -import org.junit.Ignore; import static org.junit.Assert.*; public class Chapter08Test { @Test - @Ignore - public void testJig() { - Parser parser = new Parser(""" -// Insert test case here -"""); - StopNode stop = parser.parse(); - } - - @Test public void testEx6() { Parser parser = new Parser( """ @@ -32,7 +22,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region36,Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); Assert.assertEquals(5, GraphEvaluator.evaluate(stop, 1)); @@ -55,7 +45,7 @@ while(arg < 10) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop7,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -74,7 +64,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region34,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -90,7 +80,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -109,7 +99,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -126,7 +116,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -142,15 +132,15 @@ while( arg < 10 ) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return arg;", stop.toString()); } @Test public void testRegress2() { Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;"); - StopNode stop = parser.parse(); - assertEquals("Stop[ return 0; return 0; ]", stop.toString()); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); } @Test @@ -177,7 +167,7 @@ while(arg < 10) { } return arg; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return arg;", stop.toString()); } @@ -194,7 +184,7 @@ while(arg < 10) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return Phi(Region28,Phi(Loop7,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -210,9 +200,9 @@ while(1) { } return a; """); - StopNode stop = parser.parse(); + StopNode stop = parser.parse().iterate(); assertEquals("return (Phi(Loop7,1,Add)+1);", stop.toString()); - assertTrue(stop.ret().ctrl() instanceof RegionNode); + assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java new file mode 100644 index 00000000..5567299a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -0,0 +1,300 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.Ignore; + +import static org.junit.Assert.*; + +public class Chapter09Test { + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +int v0=0; +arg=0; +while(v0) { + while(1) if(arg*arg*0==0) {} + while(0) {} + arg=1; +} +return 0; + """); + StopNode stop = parser.parse().iterate(); + } + + @Test + public void testGVN1() { + Parser parser = new Parser( + """ +int x = arg + arg; +if(arg < 10) { + return arg + arg; +} +else { + x = x + 1; +} +return x; + """); + StopNode stop = parser.parse(); + assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); + Assert.assertEquals(2, GraphEvaluator.evaluate(stop, 1)); + Assert.assertEquals(23, GraphEvaluator.evaluate(stop, 11)); + } + + @Test + public void testGVN2() { + Parser parser = new Parser( + """ +return arg*arg-arg*arg; + """); + StopNode stop = parser.parse(); + assertEquals("return 0;", stop.toString()); + Assert.assertEquals(0, GraphEvaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist1() { + Parser parser = new Parser( + """ +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop7,arg,(Phi_arg+2));", stop.toString()); + Assert.assertEquals(11, GraphEvaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist2() { + Parser parser = new Parser( + """ +int cond = 0; +int one = 1; +while (arg < 10) { + if (cond) one = 2; + arg = arg + one*3 + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop8,arg,(Phi_arg+4));", stop.toString()); + Assert.assertEquals(13, GraphEvaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist3() { + Parser parser = new Parser( + """ +int v1 = 0; +int v2 = 0; +int v3 = 0; +int v4 = 0; +int v5 = 0; +int v6 = 0; +int v7 = 0; +int v8 = 0; +while (arg) { + if (v1) v2 = 1; + if (v2) v3 = 1; + if (v3) v4 = 1; + if (v4) v5 = 1; + if (v5) v6 = 1; + if (v6) v7 = 1; + if (v7) v8 = 1; + arg = arg + v8 + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop14,arg,(Phi_arg+1));", stop.toString()); + } + + @Test + public void testRegionPeepBug() { + Parser parser = new Parser( + """ +int v0=0; +int v1=0; +while(v1+arg) { + arg=0; + int v2=v0; + while(arg+1) {} + v0=1; + v1=v2; +} + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testWhile0() { + Parser parser = new Parser("while(0) continue; if(0) arg=0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testWhile1() { + Parser parser = new Parser(""" +if(0) while(0) { + int arg=arg; + while(0) {} +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testPrecedence() { + Parser parser = new Parser("return 3-1+2;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + } + + @Test + public void testSwap2() { + Parser parser = new Parser("return 1+(1+1);"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3;", stop.toString()); + } + + + @Test + public void testFuzz0() { + Parser parser = new Parser(""" +int one = 1; +int a = 0; +int zero = 0; +while(arg) { + a = -(one + a + 2); + arg = arg + 1; + one = one + zero; +} +return a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop9,0,(-(Phi_a+3)));", stop.toString()); + } + + @Test + public void testFuzz1() { + Parser parser = new Parser(""" +while(1) {} +while(arg) break; +while(arg) arg=0; +arg=0; +int v0=0!=0<-0; +return -0+0+0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testFuzz2() { + Parser parser = new Parser("return 0+-0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testFuzz3() { + Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz4() { + Parser parser = new Parser(""" +while(1) { + arg=0<=0; + if(1<0) while(arg==-0) arg=arg-arg; +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz5() { + Parser parser = new Parser(""" +{ + int v0=0; + while(1) + int v1=0--0; + while(v0) + break; + while(-v0) { + while(0+0+v0) continue; + break; + } + if(-0!=-0+0+v0) while(0+0+0+0) + break; +} +return 0!=0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz6() { + Parser parser = new Parser(""" +int v0=0; +while(0==1) while(v0) + v0=1+v0; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz7() { + Parser parser = new Parser(""" +while(1) {} +int v0=0; +while(v0) + {} +int v1=0; +while(1) + v1=1; +return v1+v0; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz8() { + Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop6,arg,(Phi_arg-1));", stop.toString()); + } + + @Test + public void testMeet() { + Type t1 = Type.TOP; + Type t2 = TypeInteger.TOP; + Assert.assertEquals(TypeInteger.TOP, t1.meet(t2)); + Assert.assertEquals(TypeInteger.TOP, t2.meet(t1)); + t1 = Type.BOTTOM; + t2 = TypeInteger.BOT; + Assert.assertEquals(Type.BOTTOM, t1.meet(t2)); + Assert.assertEquals(Type.BOTTOM, t2.meet(t1)); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index 82a6a472..bb3bec1c 100644 --- a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -1,8 +1,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.fuzzer.Fuzzer; -import org.junit.Ignore; import org.junit.Test; +import org.junit.Ignore; import static org.junit.Assert.assertTrue; @@ -18,9 +18,18 @@ public class FuzzerWrap { @Ignore public void fuzzPeeps() { var fuzzer = new Fuzzer(); - for (int i=0; i<100000; i++) + for (int i=0; i<1000000; i++) fuzzer.fuzzPeeps(i); assertTrue(fuzzer.noExceptions()); } + + @Test + @Ignore + public void fuzzPeepTiming() { + var fuzzer = new Fuzzer(); + int max_nid=0; + for (int i=0; i<1000000; i++) + max_nid = fuzzer.fuzzPeepTiming(i, max_nid); + } } diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 5fcd8764..936fb41e 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -1,6 +1,8 @@ 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; @@ -108,6 +110,45 @@ public class Fuzzer { } /** + * 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 */ @@ -118,6 +159,7 @@ public class Fuzzer { check(sb.toString(), valid); } + /** * Check that no exceptions happened. * @return true if no exception happened diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index 37a57e11..f13a51e1 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -1,15 +1,79 @@ 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) { @@ -50,9 +114,25 @@ class FuzzerUtils { * Parse script with peepholes enabled or disabled */ public static StopNode parse(String script, boolean runPeeps) { - var parser = new Parser(script); - Node._disablePeephole = !runPeeps; - return parser.parse(); + 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/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java index b7fc18ec..2ef0a3fd 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java @@ -32,8 +32,8 @@ public class Reducer { new Replace(Pattern.compile("\\{(?:[^{}]|\\{})+}"), "{}"), new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)}"), "$1"), new Replace(Pattern.compile("\\(([^()]+)\\)"), "$1"), - new Replace(Pattern.compile("\\b\\w+\\b"), "1"), new Replace(Pattern.compile("\\b\\w+\\b"), "0"), + new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "1"), new Replace(Pattern.compile("\\d+\\+"), ""), new Replace(Pattern.compile("\\+\\d+"), ""), new Replace(Pattern.compile("(?<=\\n|^)[^\\n]*(?=\\n|$)"), "\n"), diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 148c2319..ad98f7e2 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -16,20 +16,6 @@ import java.util.Random; public class ScriptGenerator { /** - * Get the list of keywords from the parser. - */ - @SuppressWarnings("unchecked") - private static HashSet<String> getKeywords() { - try { - var field = Parser.class.getDeclaredField("KEYWORDS"); - field.setAccessible(true); - return (HashSet<String>) field.get(new Parser("")); - } catch (Throwable e) { - throw new ExceptionInInitializerError(e); - } - } - - /** * Number of spaces per indentation */ private static final int INDENTATION = 4; @@ -40,7 +26,15 @@ public class ScriptGenerator { /** * List of keywords not valid for identifiers. */ - private static final HashSet<String> KEYWORDS = getKeywords(); + 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. @@ -83,11 +77,11 @@ public class ScriptGenerator { /** * The depth of blocks allowed. */ - private int depth = 7; + private int depth = 20; /** * The depth of expressions allowed. */ - private int exprDepth = 3; + private int exprDepth = 30; /** * Current variables in scope. */ @@ -141,7 +135,7 @@ public class ScriptGenerator { * @return The random name generated */ private StringBuilder getRandomName() { - int len = random.nextInt(10) + 1; + 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++) @@ -203,7 +197,7 @@ public class ScriptGenerator { * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated */ public int genStatements() { - var num = random.nextInt(10); + var num = random.nextInt(10); // (30); for (int i=0; i<num; i++) { printIndentation(); var stop = genStatement(); |
