diff options
| author | Dibyendu Majumdar <dibyendumajumdar@users.noreply.github.com> | 2024-06-30 01:58:08 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-30 01:58:08 +0100 |
| commit | 00d1b8eed7406fc8e150dd5f789cfac48db14286 (patch) | |
| tree | 3595692bfd3fd2da88acb2ebb421ede94501cca7 | |
| parent | fd8442075faa603b3956d51d81486424624c5355 (diff) | |
| download | Simple-00d1b8eed7406fc8e150dd5f789cfac48db14286.tar.gz Simple-00d1b8eed7406fc8e150dd5f789cfac48db14286.zip | |
Cliff's GCM implementation for chapter 11 (#88)
87 files changed, 13928 insertions, 3 deletions
@@ -38,11 +38,10 @@ The simple language will be a small subset of C. * [Chapter 8](chapter08/README.md): Looping construct continued, lazy phi creation, `break` and `continue` statements. * [Chapter 9](chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists. * [Chapter 10](chapter10/README.md): 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: Nested references in Structs. Float type. -* Chapter 12: One dimensional static length array type. Array load/store. String type. +* [Chapter 11](chapter11/README.md): Global Code Motion - Scheduling. +* Chapter 12: Nested references in Structs. Float type. One dimensional static length array type. Array load/store. * Chapter 13: Functions and calls. * Chapter 14: Boolean operators `&&` and `||` including short circuit. -* Chapter 15: Global Code Motion - unwind SoN to CFG. Scheduling. * Chapter 16: Code generation: perhaps to Java bytecodes. * Chapter 17: Code generation: to native X86 or ARM. Instruction selection, BURS. Register allocation. * Chapter 18: Exceptions diff --git a/chapter11/Makefile b/chapter11/Makefile new file mode 100644 index 00000000..c7724bc7 --- /dev/null +++ b/chapter11/Makefile @@ -0,0 +1,98 @@ +SHELL := /bin/bash +.DELETE_ON_ERROR: + +# for printing variable values +# usage: make print-VARIABLE +# > VARIABLE = value_of_variable +print-% : ; @echo $* = $($*) + +# literal space +space := $() $() + +# Decide OS-specific questions +# jar-file seperator +ifeq ($(OS),Windows_NT) + SEP = ; +else + SEP = : +endif +# Find a reasonable ctags. +CTAGS = $(shell which ctags) +# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew +ifeq ($(UNAME),Darwin) + CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags) +endif + +# Fun Args to javac. Mostly limit to java8 source definitions, and fairly +# aggressive lint warnings. +JAVAC_ARGS = -g + +# Source code +SIMPLE := com/seaofnodes/simple +SRC := src/main/java +TST := src/test/java +CLZDIR:= build/classes +main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java) +test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java) +main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas)) +test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas)) +test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java)) +classes = $(main_classes) $(test_classes) +# All the libraries +libs = $(wildcard lib/*jar) +jars = $(subst $(space),$(SEP),$(libs)) + + +default_targets := $(main_classes) $(test_classes) +# Optionally add ctags to the default target if a reasonable one was found. +ifneq ($(CTAGS),) +default_targets += tags +endif + +default: $(default_targets) + +# Compile just the out-of-date files +$(main_classes): build/classes/main/%class: $(SRC)/%java + @echo "compiling " $@ " because " $? + @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main + @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas) + +$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes) + @echo "compiling " $@ " because " $? + @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test + @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas) + +# Base launch line for JVM tests +JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test" + +tests: $(default_targets) + @echo "testing " $(test_cp) + @$(JVM) org.junit.runner.JUnitCore $(test_cp) + +fuzzer: $(default_targets) + @echo "fuzzing " $(test_cp) + @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap + + +.PHONY: clean +clean: + rm -rf build + rm -f TAGS + (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0) + +# Download libs from maven +lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar + +# Jars +lib/junit-4.12.jar: + @[ -d lib ] || mkdir -p lib + @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar) + +lib/hamcrest-core-1.3.jar: + @[ -d lib ] || mkdir -p lib + @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar) + +# Build emacs tags (part of a tasty emacs ide experience) +tags: $(main_javas) $(test_javas) + @rm -f TAGS + @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST) diff --git a/chapter11/README.md b/chapter11/README.md new file mode 100644 index 00000000..a2dc753c --- /dev/null +++ b/chapter11/README.md @@ -0,0 +1,538 @@ +# Chapter 11 + +The original input source program defines a sequence in which things happen. As we parse the program into Sea of Nodes representation +and perform various optimizations, this sequence is not fully maintained. The optimized Sea of Nodes graph is driven more +by dependencies between nodes rather that the sequence of instructions in the original source program. + +Our goal in this chapter is to look at how we can recover a schedule for executing instructions from an +optimized Sea of Nodes graph. This schedule needs to preserve the semantics of the original source program, +but is otherwise allowed to reorder the execution of instructions. + +Since the scheduling algorithm works across the implicit Basic Blocks in the SoN Graph, +we call this a Global Code Motion algorithm; the scheduler can move instructions across Basic Block boundaries. + +The primary reference for this algorithm is the GCM GVN paper [^1]. However, the implementation in Simple has some +differences compared to the version described in the paper. + +Since this is a complex topic, we will present a high level summary first and then delve into the details. + +## High Level Overview + +The Sea of Nodes graph has two virtual graphs within it. + +* There is a control flow graph, represented by certain node types, such as Start, If, Region, etc. +* There is a data graph, primarily driven by Def-Use edges between nodes that produce or consume values or memory. + +From a scheduling point of view, the control flow graph is fixed, and immovable. +However, the nodes that consume or produce data values/memory have some flexibility in terms of when they are executed. The goal of the +scheduling algorithm is to find the best placement of these nodes so that they are both optimum and correct. + +At its core, the scheduling algorithm works in two phases: + +* Schedule Early - in this phase, we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the + first control block where they are dominated by their inputs. +* Schedule Late - in this phase we do a downward DFS walk on the "outputs" of each Node starting from the top (Start), and move data nodes to a block between the first block calculated above, + and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. + Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. + +## Scheduling Walk Through + +Before we describe the steps in detail, it is instructive to walk through an example and see the changes that occur to the SoN graph during scheduling. + +To motivate the discussion, we will use this example program. + +```java +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; +``` + +First lets look at the graph before scheduling. + + + +Observe that + +* Control nodes are colored in yellow; these are immovable. +* New nodes have control input and therefore these are already scheduled. +* Ditto for Phi nodes which are attached to the Region nodes. +* So what remains are the "floating" Data nodes that do not have a control input at this stage. In this example, these are the load `.f` and store `.f=` nodes. + +Now, lets look at the graph after we run the early schedule. + + + + +* Observe that the load `.f` and the stores `.f=` now have control edges to the `$ctrl` projection from Start. Thus, the early schedule has put the Data nodes in the first basic block. +* This is because the inputs to these nodes are have the `$ctrl` projection as the immediate dominator. + +The graph below shows the schedule post late scheduling. + + + +The snip below shows the main changes in the graph: + + + +* The store `.f=` now has an anti-dependency on the load `.f`; this ensures that the store is scheduled after the load, as required by program semantics. We discuss anti-dependencies in detail later. +* Observe also that the store `.f=` is now bound to the True branch of the If node. + +## Scheduling a Loop + +We show another example, this time involving a loop. + +```java +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; +``` + +The SoN graph prior to scheduling looks like this: + + + +Following early schedule generation, we get: + + + +Note that the `arg === 5` comparison at this stage is not in the correct place. +This is rectified after we complete late scheduling. + + + +The final execution schedule shows that the expression `i/3` can be performed outside the loop because it only depends on the +original value of `arg`, and hence is loop invariant. + +## Components of the Global Code Motion Algorithm + +We have already alluded to several components of the GCM algorithm in passing above. Here we list them out as well as others we did not mention: + +* The SoN graph has implicit basic blocks in the control flow nodes. For the GCM algo, we need to recognize these nodes more explicitly. +* When a program has an infinite loop, it poses a problem for the algo, as nodes can be unreachable from the Stop node. To work around this issue, we need to discover + infinite loops and create a dummy edge connecting the loop to the Stop node. +* Loops are already identified in the Simple SoN graph, so we do not need a loop discovery step. However, we need to compute the loop depth associated with each CFG node. +* In [Chapter 6](../chapter06/README.md) we explained the concept of Dominators. Dominators are key to the GCM algo, and we extend our incremental dominator discovery algo to + ensure that it meets the requirements of the algo (TODO did we enhance it in this chapter?). +* We already mentioned the two phases of the algo - the Early Scheduling and the Late Scheduling. +* In addition, we need to add anti-dependency edges between Loads and Stores in certain scenarios to enforce correct execution order. +* There are a few changes to our Node hierarchy to help us implement the GCM algo more conveniently. These changes do not conceptually alter the Node hierarchy we inherited from the previous chapters. + +## Identification of Basic Blocks in SoN graph + +The Sea of Nodes graph already captures the programs control flow graph. This information is implicit in the control nodes and edges from control nodes to other types of nodes. + +To recap, Control starts at the Start node, via a projection that is bound to the name `$ctrl`. As the control flows in the program, this name binding gets updated, and control is +passed around, until it reaches the Stop node. + +The Basic Block structure of the CFG can be easily constructed by recognizing that certain control nodes start a Basic Block, whereas certain others end a BB. + +Here is the list of all control nodes: + +| Node | Starts a BB | Ends a BB | +|--------|-----------------------------------------|-----------| +| Start | Yes but only via the `$ctrl` projection | No | +| CProj | Yes if input control is an If node | No | +| Region | Yes | No | +| If | No | Yes | +| Return | No | Yes | +| Stop | Yes | Yes | + +Changes to Node hierarchy in this chapter: + +* All control nodes above now extend a base class CFG Node. This allows us to place common functionality of control flow nodes in the base class. +* The CProj node extends the regular Proj node and is used in following cases: + * The `$ctrl` projection off Start + * The True and False projections off If. +* The Never node is a special sub class of If that is used to handle infinite loops as explained later. +* The XCtrl node represents a dead control. + +## Handling Infinite Loops + +Here is an example of code that contains an infinite loop: + +```java +while (1) {} +return 0; +``` + +First lets look at the graph resulting from this: + + + +Now, look at the modified graph after we insert an edge from the infinite loop to Stop node. + + + +The implementation is in the Loop node: + +```java + // If this is an unreachable loop, it may not have an exit. If it does not + // (i.e., infinite loop), force an exit to make it reachable. + public void forceExit( StopNode stop ) { + // Walk the backedge, then immediate dominator tree util we hit this + // Loop again. If we ever hit a CProj from an If (as opposed to + // directly on the If) we found our exit. + CFGNode x = back(); + while( x != this ) { + if( x instanceof CProjNode exit ) + return; // Found an exit, not an infinite loop + x = x.idom(); + } + // Found a no-exit loop. Insert an exit + NeverNode iff = new NeverNode(back()); + for( Node use : _outputs ) + if( use instanceof PhiNode phi ) + iff.addDef(use); + CProjNode t = new CProjNode(iff,0,"True" ); + CProjNode f = new CProjNode(iff,1,"False"); + setDef(2,f); + stop.addDef(new ReturnNode(t,Parser.ZERO,null)); + } +``` + +## Dominators + +In Simple, we compute Dominators incrementally. Our approach relies on the fact that we may delete parts of the graph during peepholes, +but we never introduce new control structure via peepholes. This allows us to use a simple approach described below. + +The CFG node is the base class for all control nodes. It maintains a conservative approximation of dominator depth via `_idepth`. This field is a cached value representing +the immediate dominator depth. Its initial value is `0`, which signifies that it has not yet been computed. On request, we compute this as shown below. + +```java +class CFGNode { + public int _idepth; + public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; } +} +class RegionNode extends CFGNode { + // Immediate dominator of Region is a little more complicated. + @Override public int idepth() { + if( _idepth!=0 ) return _idepth; + int d=0; + for( Node n : _inputs ) + if( n!=null ) + d = Math.max(d,((CFGNode)n).idepth()+1); + return _idepth=d; + } +} +class StartNode extends CFGNode { + @Override public int idepth() { return 0; } +} +class StopNode extends CFGNode { + @Override public int idepth() { + if( _idepth!=0 ) return _idepth; + int d=0; + for( Node n : _inputs ) + if( n!=null ) + d = Math.max(d,((CFGNode)n).idepth()+1); + return _idepth=d; + } +} +``` +If portions of the control flow graph are deleted, then there will be gaps in the `_idepth`, but it still correctly reflects the +invariant that the value of `_idepth` increases as we go down the dominator tree. + +Alongside the dominator depth, which is cached on first compute, a method is provided to get the immediate Dominator node. This +value is not cached as it is only valid at a point in time, and is invalidated as the graph changes. + +We show the code that computes this value: + +```java +class CFGNode { + // Return the immediate dominator of this Node and compute dom tree depth. + public CFGNode idom() { return cfg(0); } + // Return the LCA of two idoms + public CFGNode idom(CFGNode rhs) { + if( rhs==null ) return this; + CFGNode lhs = this; + while( lhs != rhs ) { + var comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } +} +class RegionNode extends CFGNode { + @Override public CFGNode idom() { + CFGNode lca = null; + // Walk the LHS & RHS idom trees in parallel until they match, or either fails. + // Because this does not cache, it can be linear in the size of the program. + for( int i=1; i<nIns(); i++ ) + lca = cfg(i).idom(lca); + return lca; + } +} +class StartNode extends CFGNode { + @Override public CFGNode idom() { return null; } +} +class StopNode extends CFGNode { + @Override public CFGNode idom() { return null; } +} +``` + +## Loop Depth + +Simple's Sea of Nodes graph identifies loops explicitly via Loop nodes. Since the language provides a single way to create a loop, using the `while` statement, +it is not necessary to implement a generic loop discovery process. + +We do however need to compute a loop depth. This is done similar to how we compute the dominator depth. + +```java +class CFGNode { + // Loop nesting depth + public int _loopDepth; + public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(0).loopDepth()) : _loopDepth; } +} +class RegionNode extends CFGNode { + @Override public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(1).loopDepth()) : _loopDepth; } +} +class StartNode extends CFGNode { + @Override public int loopDepth() { return (_loopDepth=1); } +} +class StopNode extends CFGNode { + @Override public int loopDepth() { return (_loopDepth=1); } +} +``` + +## Early Schedule + +The GCM algorithm proper starts with the computation of the early schedule, during which do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the +first control block where they are dominated by their inputs. + +A pre-condition of this is to ensure that infinite loops have been "fixed" as described earlier. + +The implementation of early schedule is shown below: + +```java + // ------------------------------------------------------------------------ +private static void schedEarly() { + ArrayList<CFGNode> rpo = new ArrayList<>(); + BitSet visit = new BitSet(); + _rpo_cfg(Parser.START, visit, rpo); + // Reverse Post-Order on CFG + for( int j=rpo.size()-1; j>=0; j-- ) { + CFGNode cfg = rpo.get(j); + cfg.loopDepth(); + for( Node n : cfg._inputs ) + _schedEarly(n,visit); + // Strictly for dead infinite loops, we can have entire code blocks + // not reachable from below - so we reach down, from above, one + // step. Since _schedEarly modifies the output arrays, the normal + // region._outputs ArrayList iterator throws CME. The extra edges + // are always *added* after any Phis, so just walk the Phi prefix. + if( cfg instanceof RegionNode region ) { + int len = region.nOuts(); + for( int i=0; i<len; i++ ) + if( region.out(i) instanceof PhiNode phi ) + _schedEarly(phi,visit); + } + } +} + +// Post-Order of CFG +private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) { + if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) + return; // Been there, done that + visit.set(cfg._nid); + for( Node use : cfg._outputs ) + _rpo_cfg(use,visit,rpo); + rpo.add(cfg); +} + +private static void _schedEarly(Node n, BitSet visit) { + if( n==null || visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + // Schedule not-pinned not-CFG inputs before self. Since skipping + // Pinned, this never walks the backedge of Phis (and thus spins around + // a data-only loop, eventually attempting relying on some pre-visited- + // not-post-visited data op with no scheduled control. + for( Node def : n._inputs ) + if( def!=null && !def.isPinned() ) + _schedEarly(def,visit); + // If not-pinned (e.g. constants, projections, phi) and not-CFG + if( !n.isPinned() ) { + // Schedule at deepest input + CFGNode early = Parser.START; // Maximally early, lowest idepth + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i).cfg0().idepth() > early.idepth() ) + early = n.in(i).cfg0(); // Latest/deepest input + n.setDef(0,early); // First place this can go + } +} +``` + +* The early schedule populates the control input for data nodes - recall that this is null up to this point as data nodes are not attached to any control flow nodes until now. +* Most of the code above is about walking the graph in the right order. The actual computation of the early schedule is in these lines: + +```java + // If not-pinned (e.g. constants, projections, phi) and not-CFG + if( !n.isPinned() ) { + // Schedule at deepest input + CFGNode early = Parser.START; // Maximally early, lowest idepth + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i).cfg0().idepth() > early.idepth() ) + early = n.in(i).cfg0(); // Latest/deepest input + n.setDef(0,early); // First place this can go + } +``` + +## Late Schedule + +During this phase we do a downward DFS walk on the "outputs" of each Node starting from the top (Start), and move data nodes to a block between the first block in the early schedule, +and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. +Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. + +The code for computing the late schedule is shown below. + +```java + // ------------------------------------------------------------------------ + private static void schedLate(StartNode start) { + CFGNode[] late = new CFGNode[Node.UID()]; + Node[] ns = new Node[Node.UID()]; + _schedLate(start,ns,late); + for( int i=0; i<late.length; i++ ) + if( ns[i] != null ) + ns[i].setDef(0,late[i]); + } + + // Forwards post-order pass. Schedule all outputs first, then draw a + // idom-tree line from the LCA of uses to the early schedule. Schedule is + // legal anywhere on this line; pick the most control-dependent (largest + // idepth) in the shallowest loop nest. + private static void _schedLate(Node n, Node[] ns, CFGNode[] late) { + if( late[n._nid]!=null ) return; // Been there, done that + // These I know the late schedule of, and need to set early for loops + if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); + if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); + + // Walk Stores before Loads, so we can get the anti-deps right + for( Node use : n._outputs ) + if( isForwardsEdge(use,n) && + use._type instanceof TypeMem ) + _schedLate(use,ns,late); + // Walk everybody now + for( Node use : n._outputs ) + if( isForwardsEdge(use,n) ) + _schedLate(use,ns,late); + // Already implicitly scheduled + if( n.isPinned() ) return; + // Need to schedule n + + // Walk uses, gathering the LCA (Least Common Ancestor) of uses + CFGNode early = (CFGNode)n.in(0); + assert early != null; + CFGNode lca = null; + for( Node use : n._outputs ) + lca = use_block(n,use, late).idom(lca); + + // Loads may need anti-dependencies, raising their LCA + if( n instanceof LoadNode load ) + lca = find_anti_dep(lca,load,early,late); + + // Walk up from the LCA to the early, looking for best place. This is + // lowest execution frequency, approximated by least loop depth and + // deepest control flow. + CFGNode best = lca; + lca = lca.idom(); // Already found best for starting LCA + for( ; lca != early.idom(); lca = lca.idom() ) + if( better(lca,best) ) + best = lca; + assert !(best instanceof IfNode); + ns [n._nid] = n; + late[n._nid] = best; + } + + // Block of use. Normally from late[] schedule, except for Phis, which go + // to the matching Region input. + private static CFGNode use_block(Node n, Node use, CFGNode[] late) { + if( !(use instanceof PhiNode phi) ) + return late[use._nid]; + CFGNode found=null; + for( int i=1; i<phi.nIns(); i++ ) + if( phi.in(i)==n ) + if( found==null ) found = phi.region().cfg(i); + else Utils.TODO(); // Can be more than once + assert found!=null; + return found; + } + + // Least loop depth first, then largest idepth + private static boolean better( CFGNode lca, CFGNode best ) { + return lca._loopDepth < best._loopDepth || + (lca.idepth() > best.idepth() || best instanceof IfNode); + } + + // Skip iteration if a backedge + private static boolean isForwardsEdge(Node use, Node def) { + return use != null && def != null && + !(use.nIns()>2 && use.in(2)==def && (use instanceof LoopNode || (use instanceof PhiNode phi && phi.region() instanceof LoopNode))); + } +``` + +## Inserting Anti Dependencies + +To ensure that Loads and Stores to the same memory location are correctly ordered, +we insert anti-dependencies when a Load's path up the dominator tree crosses the path of a Store that is the user of the Load's memory input. + +```java + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) { + // We could skip final-field loads here. + // Walk LCA->early, flagging Load's block location choices + for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) + cfg._anti = load._nid; + // Walk load->mem uses, looking for Stores causing an anti-dep + for( Node mem : load.mem()._outputs ) { + switch( mem ) { + case StoreNode st: + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + break; + case PhiNode phi: + // Repeat anti-dep for matching Phi inputs. + // No anti-dep edges but may raise the LCA. + for( int i=1; i<phi.nIns(); i++ ) + if( phi.in(i)==load.mem() ) + lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null); + break; + case LoadNode ld: break; // Loads do not cause anti-deps on other loads + case ReturnNode ret: break; // Load must already be ahead of Return + default: throw Utils.TODO(); + } + } + return lca; + } + + private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) { + // Walk store blocks "reach" from its scheduled location to its earliest + for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { + // Store and Load overlap, need anti-dependence + if( stblk._anti==load._nid ) { + lca = stblk.idom(lca); // Raise Loads LCA + if( lca == stblk && st != null && Utils.find(st._inputs,load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well + return lca; // Cap this stores' anti-dep to here + } + } + return lca; + } +``` + +## Video Walk Through + +Please watch the following video for a detailed walk through of the implementation. + +* [Coffee Compiler Club - 21 June 2024](https://youtu.be/5Po0gxfE7LA?feature=shared). +* [Coffee Compiler Club - 26 June 2024](https://www.youtube.com/watch?v=HnN2YzmFb-8&t=0s) + +[^1]: Cliff Click. (1995). + Global Code Motion Global Value Numbering.
\ No newline at end of file diff --git a/chapter11/docs/11-grammar.md b/chapter11/docs/11-grammar.md new file mode 100644 index 00000000..0aaf0619 --- /dev/null +++ b/chapter11/docs/11-grammar.md @@ -0,0 +1,128 @@ +# Grammar for Chapter 11 + +```antlrv4 +grammar SimpleLanguage; + +program + : statement+ EOF + ; + +statement + : returnStatement + | structDeclaration + | declStatement + | blockStatment + | expressionStatement + | ifStatement + | whileStatement + | breakStatement + | continueStatment + | metaStatement + ; + +field + : 'int' IDENTIFIER ';' + ; + +fields + : field+ + ; + +structDeclaration + : 'struct' IDENTIFIER '{' fields '}' + ; + +whileStatement + : 'while' '(' expression ')' statement + ; + +breakStatement + : 'break' ';' + ; + +continueStatement + : 'continue' ';' + ; + +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + +metaStatement + : '#showGraph' ';' + ; + +expressionStatement + : IDENTIFIER '=' expression ';' + | fieldExpression '=' expression ';' + ; + +blockStatement + : '{' statement+ '}' + ; + +structName + : IDENTIFIER + ; + +declStatement + : 'int' IDENTIFIER '=' expression ';' + | structName IDENTIFIER ('?')? '=' expression ';' + ; + +returnStatement + : 'return' expression ';' + ; + +expression + : comparisonExpression + ; + +comparisonExpression + : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+' | '-') multiplicativeExpression)* + ; + +multiplicativeExpression + : unaryExpression (('*' | '/') unaryExpression)* + ; + +unaryExpression + : ('-') unaryExpression + | primaryExpression + ; + +newExpression + : 'new' IDENTIFIER + ; + +fieldExpression + : primaryExpresson '.' IDENTIFIER + ; + +primaryExpression + : IDENTIFIER + | INTEGER_LITERAL + | 'true' + | 'false' + | 'null' + | newExpression + | '(' expression ')' + | fieldExpression + ; + +INTEGER_LITERAL + : [1-9][0-9]* + | [0] + ; + +IDENTIFIER + : NON_DIGIT (NON_DIGIT | DIGIT)* + ; + +NON_DIGIT: [a-zA-Z_]; +DEC_DIGIT: [0-9]; +```
\ No newline at end of file diff --git a/chapter11/docs/Makefile b/chapter11/docs/Makefile new file mode 100644 index 00000000..12659b8f --- /dev/null +++ b/chapter11/docs/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/chapter11/docs/build-graphs.sh b/chapter11/docs/build-graphs.sh new file mode 100644 index 00000000..839a36ba --- /dev/null +++ b/chapter11/docs/build-graphs.sh @@ -0,0 +1,9 @@ +dot -Tsvg graph1.gv > graph1.svg +dot -Tsvg graph2.gv > graph2.svg +dot -Tsvg graph3.gv > graph3.svg +dot -Tsvg graph4.gv > graph4.svg +dot -Tsvg graph5.gv > graph5.svg +dot -Tsvg graph6.gv > graph6.svg +dot -Tsvg graph7.gv > graph7.svg +dot -Tsvg graph8.gv > graph8.svg + diff --git a/chapter11/docs/graph1.gv b/chapter11/docs/graph1.gv new file mode 100644 index 00000000..97815f63 --- /dev/null +++ b/chapter11/docs/graph1.gv @@ -0,0 +1,76 @@ +digraph chapter11 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + <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_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p1[taillabel=1]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=1]; + Return25 -> Phi_224[taillabel=2 color=blue]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph1.svg b/chapter11/docs/graph1.svg new file mode 100644 index 00000000..592db961 --- /dev/null +++ b/chapter11/docs/graph1.svg @@ -0,0 +1,285 @@ +<?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: chapter11 Pages: 1 --> +<svg width="336pt" height="636pt" + viewBox="0.00 0.00 336.00 636.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 632)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-632 332,-632 332,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-620 320,-620 320,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="198,-583 198,-608 304,-608 304,-583 198,-583"/> +<polygon fill="none" stroke="black" points="198,-583 198,-608 304,-608 304,-583 198,-583"/> +<text text-anchor="start" x="232.5" y="-591.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="198,-552 198,-583 304,-583 304,-552 198,-552"/> +<polygon fill="yellow" stroke="transparent" points="203,-557 203,-578 243,-578 243,-557 203,-557"/> +<polygon fill="none" stroke="black" points="203,-557 203,-578 243,-578 243,-557 203,-557"/> +<text text-anchor="start" x="206" y="-563.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="243,-557 243,-578 274,-578 274,-557 243,-557"/> +<text text-anchor="start" x="246" y="-563.8" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="274,-557 274,-578 299,-578 299,-557 274,-557"/> +<text text-anchor="start" x="277" y="-563.8" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="210,-52 156,-52 156,-16 210,-16 210,-52"/> +<text text-anchor="middle" x="183" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return25 --> +<g id="node15" class="node"> +<title>Return25</title> +<polygon fill="yellow" stroke="black" points="216,-124 150,-124 150,-88 216,-88 216,-124"/> +<text text-anchor="middle" x="183" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return25 --> +<g id="edge1" class="edge"> +<title>Stop3->Return25</title> +<path fill="none" stroke="red" d="M183,-52.3C183,-60.02 183,-69.29 183,-77.89"/> +<polygon fill="red" stroke="red" points="179.5,-77.9 183,-87.9 186.5,-77.9 179.5,-77.9"/> +<text text-anchor="middle" x="180" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="137" cy="-422" rx="27" ry="18"/> +<text text-anchor="middle" x="137" y="-418.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="151" cy="-494" rx="38.99" ry="18"/> +<text text-anchor="middle" x="151" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M155.04,-512.06C160.38,-530.53 171.62,-558.28 191.99,-565.33"/> +<polygon fill="green" stroke="green" points="191.56,-568.8 202,-567 192.71,-561.9 191.56,-568.8"/> +<text text-anchor="middle" x="152.04" y="-514.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_11 --> +<g id="node5" class="node"> +<title>Con_11</title> +<ellipse fill="none" stroke="black" cx="255" cy="-494" rx="27" ry="18"/> +<text text-anchor="middle" x="255" y="-490.3" font-family="Times,serif" font-size="14.00">#2</text> +</g> +<!-- Store12 --> +<g id="node6" class="node"> +<title>Store12</title> +<ellipse fill="none" stroke="black" cx="230" cy="-422" rx="27.9" ry="18"/> +<text text-anchor="middle" x="230" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store12->Start2 --> +<g id="edge3" class="edge"> +<title>Store12->Start2:p2</title> +<path fill="none" stroke="blue" d="M222.43,-439.51C215.04,-458.25 206.57,-489.16 219,-512 234.25,-540.04 275.92,-524.64 285.17,-545.89"/> +<polygon fill="blue" stroke="blue" points="281.77,-546.79 287,-556 288.66,-545.54 281.77,-546.79"/> +<text text-anchor="middle" x="219.43" y="-442.11" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store12->new9 --> +<g id="edge4" class="edge"> +<title>Store12->new9</title> +<path fill="none" stroke="black" d="M214.01,-437.17C203.14,-446.8 188.53,-459.75 176.11,-470.75"/> +<polygon fill="black" stroke="black" points="173.5,-468.39 168.33,-477.64 178.14,-473.63 173.5,-468.39"/> +<text text-anchor="middle" x="211.01" y="-439.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store12->Con_11 --> +<g id="edge5" class="edge"> +<title>Store12->Con_11</title> +<path fill="none" stroke="black" d="M236.05,-439.95C238.91,-447.94 242.38,-457.67 245.57,-466.6"/> +<polygon fill="black" stroke="black" points="242.34,-467.97 249,-476.21 248.94,-465.62 242.34,-467.97"/> +<text text-anchor="middle" x="233.05" y="-442.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- new13 --> +<g id="node7" class="node"> +<title>new13</title> +<ellipse fill="none" stroke="black" cx="55" cy="-494" rx="38.99" ry="18"/> +<text text-anchor="middle" x="55" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new13->Start2 --> +<g id="edge6" class="edge"> +<title>new13->Start2:p0</title> +<path fill="none" stroke="green" d="M72.65,-510.27C97.21,-530 144.07,-562.34 191.81,-566.54"/> +<polygon fill="green" stroke="green" points="191.85,-570.05 202,-567 192.17,-563.06 191.85,-570.05"/> +<text text-anchor="middle" x="69.65" y="-512.87" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store14 --> +<g id="node8" class="node"> +<title>Store14</title> +<ellipse fill="none" stroke="black" cx="136" cy="-336" rx="27.9" ry="18"/> +<text text-anchor="middle" x="136" y="-332.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store14->Con_4 --> +<g id="edge9" class="edge"> +<title>Store14->Con_4</title> +<path fill="none" stroke="black" d="M136.21,-354.4C136.34,-365.74 136.52,-380.77 136.67,-393.68"/> +<polygon fill="black" stroke="black" points="133.18,-393.95 136.8,-403.9 140.18,-393.86 133.18,-393.95"/> +<text text-anchor="middle" x="133.21" y="-357" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store14->Store12 --> +<g id="edge7" class="edge"> +<title>Store14->Store12</title> +<path fill="none" stroke="blue" d="M151.95,-351.25C166.89,-364.6 189.42,-384.73 206.44,-399.94"/> +<polygon fill="blue" stroke="blue" points="204.5,-402.91 214.29,-406.96 209.17,-397.69 204.5,-402.91"/> +<text text-anchor="middle" x="148.95" y="-353.85" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store14->new13 --> +<g id="edge8" class="edge"> +<title>Store14->new13</title> +<path fill="none" stroke="black" d="M127.49,-353.24C120.39,-366.8 110.02,-386.65 101,-404 89.97,-425.23 77.52,-449.33 68.35,-467.1"/> +<polygon fill="black" stroke="black" points="65.1,-465.77 63.63,-476.26 71.32,-468.97 65.1,-465.77"/> +<text text-anchor="middle" x="124.49" y="-355.84" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16 --> +<g id="node9" class="node"> +<title>Load16</title> +<ellipse fill="none" stroke="black" cx="183" cy="-250" rx="27" ry="18"/> +<text text-anchor="middle" x="183" y="-246.3" font-family="Times,serif" font-size="14.00">.f</text> +</g> +<!-- Load16->new9 --> +<g id="edge11" class="edge"> +<title>Load16->new9</title> +<path fill="none" stroke="black" d="M184.02,-268.21C185.58,-301.74 187.07,-378 173,-440 170.94,-449.08 167.45,-458.59 163.85,-467.01"/> +<polygon fill="black" stroke="black" points="160.59,-465.75 159.66,-476.3 166.96,-468.63 160.59,-465.75"/> +<text text-anchor="middle" x="181.02" y="-270.81" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16->Store14 --> +<g id="edge10" class="edge"> +<title>Load16->Store14</title> +<path fill="none" stroke="blue" d="M173.94,-267.2C167.18,-279.28 157.81,-296.02 150.08,-309.84"/> +<polygon fill="blue" stroke="blue" points="146.86,-308.42 145.03,-318.85 152.97,-311.84 146.86,-308.42"/> +<text text-anchor="middle" x="170.94" y="-269.8" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If17 --> +<g id="node10" class="node"> +<title>If17</title> +<polygon fill="yellow" stroke="transparent" points="210,-339 210,-364 304,-364 304,-339 210,-339"/> +<polygon fill="none" stroke="black" points="210,-339 210,-364 304,-364 304,-339 210,-339"/> +<text text-anchor="start" x="251.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="210,-308 210,-339 304,-339 304,-308 210,-308"/> +<polygon fill="yellow" stroke="transparent" points="215,-313 215,-334 255,-334 255,-313 215,-313"/> +<polygon fill="none" stroke="black" points="215,-313 215,-334 255,-334 255,-313 215,-313"/> +<text text-anchor="start" x="218" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="255,-313 255,-334 299,-334 299,-313 255,-313"/> +<polygon fill="none" stroke="black" points="255,-313 255,-334 299,-334 299,-313 255,-313"/> +<text text-anchor="start" x="258" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If17->Start2 --> +<g id="edge12" class="edge"> +<title>If17->Start2:p0</title> +<path fill="none" stroke="red" d="M274.94,-368.3C293.31,-404.87 315.92,-466.19 291,-512 275.75,-540.04 234.08,-524.64 224.83,-545.89"/> +<polygon fill="red" stroke="red" points="221.34,-545.54 223,-556 228.23,-546.79 221.34,-545.54"/> +<text text-anchor="middle" x="271.94" y="-370.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_21 --> +<g id="node11" class="node"> +<title>Con_21</title> +<ellipse fill="none" stroke="black" cx="43" cy="-336" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Store22 --> +<g id="node12" class="node"> +<title>Store22</title> +<ellipse fill="none" stroke="black" cx="99" cy="-250" rx="27.9" ry="18"/> +<text text-anchor="middle" x="99" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store22->new9 --> +<g id="edge15" class="edge"> +<title>Store22->new9</title> +<path fill="none" stroke="black" d="M95.12,-268.01C88.19,-302.16 76.55,-380.87 101,-440 105.95,-451.96 114.93,-462.83 123.93,-471.57"/> +<polygon fill="black" stroke="black" points="121.66,-474.24 131.41,-478.38 126.37,-469.06 121.66,-474.24"/> +<text text-anchor="middle" x="92.12" y="-270.61" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store22->Store14 --> +<g id="edge14" class="edge"> +<title>Store22->Store14</title> +<path fill="none" stroke="blue" d="M106.31,-267.6C111.53,-279.45 118.65,-295.62 124.61,-309.15"/> +<polygon fill="blue" stroke="blue" points="121.47,-310.7 128.71,-318.44 127.88,-307.88 121.47,-310.7"/> +<text text-anchor="middle" x="103.31" y="-270.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store22->Con_21 --> +<g id="edge16" class="edge"> +<title>Store22->Con_21</title> +<path fill="none" stroke="black" d="M88.47,-266.8C80.25,-279.12 68.7,-296.45 59.3,-310.55"/> +<polygon fill="black" stroke="black" points="56.12,-309.01 53.49,-319.27 61.95,-312.89 56.12,-309.01"/> +<text text-anchor="middle" x="85.47" y="-269.4" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Region23 --> +<g id="node13" class="node"> +<title>Region23</title> +<polygon fill="yellow" stroke="black" points="268,-196 202,-196 202,-160 268,-160 268,-196"/> +<text text-anchor="middle" x="235" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Region23->If17 --> +<g id="edge17" class="edge"> +<title>Region23->If17:p0</title> +<path fill="none" stroke="red" d="M235,-196.22C235,-219.49 235,-262.58 235,-301.64"/> +<polygon fill="red" stroke="red" points="231.5,-302 235,-312 238.5,-302 231.5,-302"/> +<text text-anchor="middle" x="232" y="-198.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region23->If17 --> +<g id="edge18" class="edge"> +<title>Region23->If17:p1</title> +<path fill="none" stroke="red" d="M244.11,-196.1C255.5,-218.96 273.84,-261.33 276.64,-301.77"/> +<polygon fill="red" stroke="red" points="273.15,-302.13 277,-312 280.14,-301.88 273.15,-302.13"/> +<text text-anchor="middle" x="241.11" y="-198.7" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224 --> +<g id="node14" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="131" cy="-178" rx="33.29" ry="18"/> +<text text-anchor="middle" x="131" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_224->Store14 --> +<g id="edge21" class="edge"> +<title>Phi_224->Store14</title> +<path fill="none" stroke="blue" d="M159.11,-187.55C179.74,-195.47 206.28,-209.49 219,-232 237.92,-265.48 196.57,-299.24 165.7,-318.58"/> +<polygon fill="blue" stroke="blue" points="163.69,-315.71 156.94,-323.87 167.31,-321.7 163.69,-315.71"/> +<text text-anchor="middle" x="162.11" y="-190.15" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224->Store22 --> +<g id="edge20" class="edge"> +<title>Phi_224->Store22</title> +<path fill="none" stroke="blue" d="M123.42,-195.59C119.63,-203.87 114.97,-214.08 110.73,-223.34"/> +<polygon fill="blue" stroke="blue" points="107.5,-221.98 106.53,-232.53 113.87,-224.89 107.5,-221.98"/> +<text text-anchor="middle" x="120.42" y="-198.19" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_224->Region23 --> +<g id="edge19" class="edge"> +<title>Phi_224->Region23</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M164.31,-178C173.41,-178 182.5,-178 191.6,-178"/> +<polygon fill="black" stroke="black" points="191.87,-181.5 201.87,-178 191.87,-174.5 191.87,-181.5"/> +<text text-anchor="middle" x="167.31" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Load16 --> +<g id="edge23" class="edge"> +<title>Return25->Load16</title> +<path fill="none" stroke="black" d="M183,-124.13C183,-148.33 183,-192.79 183,-221.61"/> +<polygon fill="black" stroke="black" points="179.5,-221.81 183,-231.81 186.5,-221.81 179.5,-221.81"/> +<text text-anchor="middle" x="180" y="-126.73" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return25->Region23 --> +<g id="edge22" class="edge"> +<title>Return25->Region23</title> +<path fill="none" stroke="red" d="M195.85,-124.3C202.03,-132.61 209.54,-142.72 216.32,-151.86"/> +<polygon fill="red" stroke="red" points="213.52,-153.96 222.29,-159.9 219.14,-149.78 213.52,-153.96"/> +<text text-anchor="middle" x="192.85" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Phi_224 --> +<g id="edge24" class="edge"> +<title>Return25->Phi_224</title> +<path fill="none" stroke="blue" d="M170.15,-124.3C163.79,-132.86 156.01,-143.33 149.07,-152.67"/> +<polygon fill="blue" stroke="blue" points="146.15,-150.74 142.99,-160.86 151.77,-154.92 146.15,-150.74"/> +<text text-anchor="middle" x="167.15" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph2.gv b/chapter11/docs/graph2.gv new file mode 100644 index 00000000..04d02a3f --- /dev/null +++ b/chapter11/docs/graph2.gv @@ -0,0 +1,80 @@ +digraph chapter11 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + <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_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p0[taillabel=0 color=red]; + Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Start2:p0[taillabel=0 color=red]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Start2:p0[taillabel=0 color=red]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p1[taillabel=1]; + Store22 -> Start2:p0[taillabel=0 color=red]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=1]; + Return25 -> Phi_224[taillabel=2 color=blue]; +} diff --git a/chapter11/docs/graph2.svg b/chapter11/docs/graph2.svg new file mode 100644 index 00000000..00f7b594 --- /dev/null +++ b/chapter11/docs/graph2.svg @@ -0,0 +1,309 @@ +<?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: chapter11 Pages: 1 --> +<svg width="346pt" height="636pt" + viewBox="0.00 0.00 346.00 636.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 632)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-632 342,-632 342,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-620 330,-620 330,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="137,-583 137,-608 243,-608 243,-583 137,-583"/> +<polygon fill="none" stroke="black" points="137,-583 137,-608 243,-608 243,-583 137,-583"/> +<text text-anchor="start" x="171.5" y="-591.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="137,-552 137,-583 243,-583 243,-552 137,-552"/> +<polygon fill="yellow" stroke="transparent" points="142,-557 142,-578 182,-578 182,-557 142,-557"/> +<polygon fill="none" stroke="black" points="142,-557 142,-578 182,-578 182,-557 142,-557"/> +<text text-anchor="start" x="145" y="-563.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="182,-557 182,-578 213,-578 213,-557 182,-557"/> +<text text-anchor="start" x="185" y="-563.8" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="213,-557 213,-578 238,-578 238,-557 213,-557"/> +<text text-anchor="start" x="216" y="-563.8" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="176,-52 122,-52 122,-16 176,-16 176,-52"/> +<text text-anchor="middle" x="149" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return25 --> +<g id="node15" class="node"> +<title>Return25</title> +<polygon fill="yellow" stroke="black" points="182,-124 116,-124 116,-88 182,-88 182,-124"/> +<text text-anchor="middle" x="149" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return25 --> +<g id="edge1" class="edge"> +<title>Stop3->Return25</title> +<path fill="none" stroke="red" d="M149,-52.3C149,-60.02 149,-69.29 149,-77.89"/> +<polygon fill="red" stroke="red" points="145.5,-77.9 149,-87.9 152.5,-77.9 145.5,-77.9"/> +<text text-anchor="middle" x="146" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="121" cy="-422" rx="27" ry="18"/> +<text text-anchor="middle" x="121" y="-418.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="191" cy="-494" rx="38.99" ry="18"/> +<text text-anchor="middle" x="191" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M178.53,-511.11C172.27,-520.49 165.49,-533 162.99,-546"/> +<polygon fill="green" stroke="green" points="159.51,-545.7 162,-556 166.47,-546.4 159.51,-545.7"/> +<text text-anchor="middle" x="175.53" y="-513.71" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_11 --> +<g id="node5" class="node"> +<title>Con_11</title> +<ellipse fill="none" stroke="black" cx="275" cy="-494" rx="27" ry="18"/> +<text text-anchor="middle" x="275" y="-490.3" font-family="Times,serif" font-size="14.00">#2</text> +</g> +<!-- Store12 --> +<g id="node6" class="node"> +<title>Store12</title> +<ellipse fill="none" stroke="black" cx="217" cy="-422" rx="27.9" ry="18"/> +<text text-anchor="middle" x="217" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store12->Start2 --> +<g id="edge3" class="edge"> +<title>Store12->Start2:p0</title> +<path fill="none" stroke="red" d="M239.72,-432.8C270.79,-446.95 322.54,-473.53 321,-493"/> +<path fill="none" stroke="red" d="M321,-495C315.6,-563.11 190.37,-496.28 166.07,-538.32"/> +<polygon fill="red" stroke="red" points="162.72,-537.27 162.68,-547.87 169.32,-539.61 162.72,-537.27"/> +<text text-anchor="middle" x="242.72" y="-435.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store12->new9 --> +<g id="edge5" class="edge"> +<title>Store12->new9</title> +<path fill="none" stroke="black" d="M210.84,-439.59C207.84,-447.66 204.16,-457.57 200.79,-466.65"/> +<polygon fill="black" stroke="black" points="197.5,-465.45 197.3,-476.04 204.06,-467.89 197.5,-465.45"/> +<text text-anchor="middle" x="207.84" y="-442.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store12->Con_11 --> +<g id="edge6" class="edge"> +<title>Store12->Con_11</title> +<path fill="none" stroke="black" d="M229.59,-438.19C237.26,-447.45 247.23,-459.48 255.86,-469.91"/> +<polygon fill="black" stroke="black" points="253.36,-472.37 262.44,-477.84 258.75,-467.91 253.36,-472.37"/> +<text text-anchor="middle" x="226.59" y="-440.79" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- new13 --> +<g id="node7" class="node"> +<title>new13</title> +<ellipse fill="none" stroke="black" cx="75" cy="-494" rx="38.99" ry="18"/> +<text text-anchor="middle" x="75" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new13->Start2 --> +<g id="edge7" class="edge"> +<title>new13->Start2:p0</title> +<path fill="none" stroke="green" d="M81.73,-512.06C90.25,-530.86 106.79,-559.28 130.97,-565.69"/> +<polygon fill="green" stroke="green" points="130.63,-569.17 141,-567 131.54,-562.23 130.63,-569.17"/> +<text text-anchor="middle" x="78.73" y="-514.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store14 --> +<g id="node8" class="node"> +<title>Store14</title> +<ellipse fill="none" stroke="black" cx="74" cy="-336" rx="27.9" ry="18"/> +<text text-anchor="middle" x="74" y="-332.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store14->Start2 --> +<g id="edge8" class="edge"> +<title>Store14->Start2:p0</title> +<path fill="none" stroke="red" d="M75,-423C81.23,-454.16 104.2,-450.38 123,-476 125.09,-478.85 144.12,-515.97 154.8,-538.72"/> +<polygon fill="red" stroke="red" points="151.69,-540.34 159.03,-547.99 158.06,-537.44 151.69,-540.34"/> +<text text-anchor="middle" x="72" y="-416.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store14->Con_4 --> +<g id="edge11" class="edge"> +<title>Store14->Con_4</title> +<path fill="none" stroke="black" d="M83.06,-353.2C89.82,-365.28 99.19,-382.02 106.92,-395.84"/> +<polygon fill="black" stroke="black" points="104.03,-397.84 111.97,-404.85 110.14,-394.42 104.03,-397.84"/> +<text text-anchor="middle" x="80.06" y="-355.8" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store14->Store12 --> +<g id="edge9" class="edge"> +<title>Store14->Store12</title> +<path fill="none" stroke="blue" d="M91.16,-350.58C98.61,-356.24 107.55,-362.71 116,-368 138.6,-382.16 165.37,-396.08 185.67,-406.11"/> +<polygon fill="blue" stroke="blue" points="184.27,-409.32 194.79,-410.56 187.34,-403.03 184.27,-409.32"/> +<text text-anchor="middle" x="88.16" y="-353.18" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store14->new13 --> +<g id="edge10" class="edge"> +<title>Store14->new13</title> +<path fill="none" stroke="black" d="M72.36,-354.25C71.17,-371.36 70.46,-398.26 75,-421"/> +<path fill="none" stroke="black" d="M75,-423C77.77,-436.87 78.02,-452.6 77.47,-465.63"/> +<polygon fill="black" stroke="black" points="73.97,-465.65 76.86,-475.84 80.95,-466.07 73.97,-465.65"/> +<text text-anchor="middle" x="69.36" y="-356.85" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16 --> +<g id="node9" class="node"> +<title>Load16</title> +<ellipse fill="none" stroke="black" cx="74" cy="-250" rx="27" ry="18"/> +<text text-anchor="middle" x="74" y="-246.3" font-family="Times,serif" font-size="14.00">.f</text> +</g> +<!-- Load16->Start2 --> +<g id="edge12" class="edge"> +<title>Load16->Start2:p0</title> +<path fill="none" stroke="red" d="M60.29,-266C52.02,-276 42.12,-289.86 37,-304 19.23,-353.07 6.22,-373.12 27,-421"/> +<path fill="none" stroke="red" d="M27,-423C42.75,-459.29 5.97,-478.5 27,-512 52.69,-552.92 76.85,-564.12 118.8,-566.47"/> +<polygon fill="red" stroke="red" points="118.8,-569.97 128.93,-566.86 119.08,-562.98 118.8,-569.97"/> +<text text-anchor="middle" x="57.29" y="-268.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Load16->new9 --> +<g id="edge14" class="edge"> +<title>Load16->new9</title> +<path fill="none" stroke="black" d="M27,-423C37.2,-446.5 102.74,-468.87 147.82,-481.75"/> +<polygon fill="black" stroke="black" points="147.08,-485.18 157.66,-484.5 148.97,-478.44 147.08,-485.18"/> +<text text-anchor="middle" x="24" y="-416.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16->Store14 --> +<g id="edge13" class="edge"> +<title>Load16->Store14</title> +<path fill="none" stroke="blue" d="M74,-268.4C74,-279.74 74,-294.77 74,-307.68"/> +<polygon fill="blue" stroke="blue" points="70.5,-307.9 74,-317.9 77.5,-307.9 70.5,-307.9"/> +<text text-anchor="middle" x="71" y="-271" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If17 --> +<g id="node10" class="node"> +<title>If17</title> +<polygon fill="yellow" stroke="transparent" points="220,-339 220,-364 314,-364 314,-339 220,-339"/> +<polygon fill="none" stroke="black" points="220,-339 220,-364 314,-364 314,-339 220,-339"/> +<text text-anchor="start" x="261.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="220,-308 220,-339 314,-339 314,-308 220,-308"/> +<polygon fill="yellow" stroke="transparent" points="225,-313 225,-334 265,-334 265,-313 225,-313"/> +<polygon fill="none" stroke="black" points="225,-313 225,-334 265,-334 265,-313 225,-313"/> +<text text-anchor="start" x="228" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="265,-313 265,-334 309,-334 309,-313 265,-313"/> +<polygon fill="none" stroke="black" points="265,-313 265,-334 309,-334 309,-313 265,-313"/> +<text text-anchor="start" x="268" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If17->Start2 --> +<g id="edge15" class="edge"> +<title>If17->Start2:p0</title> +<path fill="none" stroke="red" d="M258.27,-368.06C255.35,-385.41 255.35,-406.41 267,-421"/> +<text text-anchor="middle" x="255.27" y="-370.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_21 --> +<g id="node11" class="node"> +<title>Con_21</title> +<ellipse fill="none" stroke="black" cx="167" cy="-336" rx="27" ry="18"/> +<text text-anchor="middle" x="167" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Store22 --> +<g id="node12" class="node"> +<title>Store22</title> +<ellipse fill="none" stroke="black" cx="149" cy="-250" rx="27.9" ry="18"/> +<text text-anchor="middle" x="149" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store22->Start2 --> +<g id="edge17" class="edge"> +<title>Store22->Start2:p0</title> +<path fill="none" stroke="red" d="M121,-337C124.72,-350.99 120.62,-357.9 131,-368 171.82,-407.72 207.03,-371.77 254,-404 261.84,-409.38 261.07,-413.57 267,-421"/> +<path fill="none" stroke="red" d="M267,-423C291.51,-453.71 324.1,-453.83 321,-493"/> +<text text-anchor="middle" x="118" y="-330.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store22->new9 --> +<g id="edge19" class="edge"> +<title>Store22->new9</title> +<path fill="none" stroke="black" d="M138.02,-266.62C127.62,-283.37 114.49,-310.8 121,-335"/> +<path fill="none" stroke="black" d="M121,-337C124.76,-350.98 124.46,-355.08 131,-368 139.92,-385.61 147.62,-386.63 157,-404 167.83,-424.05 176.84,-448.28 182.87,-466.4"/> +<polygon fill="black" stroke="black" points="179.57,-467.59 185.98,-476.03 186.23,-465.43 179.57,-467.59"/> +<text text-anchor="middle" x="135.02" y="-269.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store22->Store14 --> +<g id="edge18" class="edge"> +<title>Store22->Store14</title> +<path fill="none" stroke="blue" d="M135.59,-266.02C124.07,-278.92 107.3,-297.71 94.16,-312.42"/> +<polygon fill="blue" stroke="blue" points="91.34,-310.32 87.29,-320.11 96.56,-314.98 91.34,-310.32"/> +<text text-anchor="middle" x="132.59" y="-268.62" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store22->Con_21 --> +<g id="edge20" class="edge"> +<title>Store22->Con_21</title> +<path fill="none" stroke="black" d="M152.64,-268C155.11,-279.5 158.41,-294.93 161.23,-308.08"/> +<polygon fill="black" stroke="black" points="157.85,-308.98 163.36,-318.03 164.69,-307.52 157.85,-308.98"/> +<text text-anchor="middle" x="149.64" y="-270.6" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Region23 --> +<g id="node13" class="node"> +<title>Region23</title> +<polygon fill="yellow" stroke="black" points="269,-196 203,-196 203,-160 269,-160 269,-196"/> +<text text-anchor="middle" x="236" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Region23->If17 --> +<g id="edge21" class="edge"> +<title>Region23->If17:p0</title> +<path fill="none" stroke="red" d="M237.91,-196.19C240.33,-219.44 244.28,-262.49 244.91,-301.62"/> +<polygon fill="red" stroke="red" points="241.42,-302.03 245,-312 248.42,-301.97 241.42,-302.03"/> +<text text-anchor="middle" x="234.91" y="-198.79" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region23->If17 --> +<g id="edge22" class="edge"> +<title>Region23->If17:p1</title> +<path fill="none" stroke="red" d="M249.35,-196.26C256.45,-206.22 264.76,-219.26 270,-232 282.41,-262.21 286.24,-273.37 286.89,-301.69"/> +<polygon fill="red" stroke="red" points="283.4,-302.04 287,-312 290.4,-301.96 283.4,-302.04"/> +<text text-anchor="middle" x="246.35" y="-198.86" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224 --> +<g id="node14" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="149" cy="-178" rx="33.29" ry="18"/> +<text text-anchor="middle" x="149" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_224->Store14 --> +<g id="edge25" class="edge"> +<title>Phi_224->Store14</title> +<path fill="none" stroke="blue" d="M163.74,-194.16C179.64,-212.44 200.82,-243.72 186,-268 167.78,-297.86 145.73,-285.55 116,-304 110.44,-307.45 104.68,-311.42 99.27,-315.34"/> +<polygon fill="blue" stroke="blue" points="97.08,-312.61 91.14,-321.38 101.25,-318.23 97.08,-312.61"/> +<text text-anchor="middle" x="160.74" y="-196.76" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224->Store22 --> +<g id="edge24" class="edge"> +<title>Phi_224->Store22</title> +<path fill="none" stroke="blue" d="M149,-196.3C149,-204.02 149,-213.29 149,-221.89"/> +<polygon fill="blue" stroke="blue" points="145.5,-221.9 149,-231.9 152.5,-221.9 145.5,-221.9"/> +<text text-anchor="middle" x="146" y="-198.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_224->Region23 --> +<g id="edge23" class="edge"> +<title>Phi_224->Region23</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M182.3,-178C185.73,-178 189.16,-178 192.58,-178"/> +<polygon fill="black" stroke="black" points="192.86,-181.5 202.86,-178 192.86,-174.5 192.86,-181.5"/> +<text text-anchor="middle" x="185.3" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Load16 --> +<g id="edge27" class="edge"> +<title>Return25->Load16</title> +<path fill="none" stroke="black" d="M133.12,-124.15C124.54,-134.07 114.25,-147.11 107,-160 95.8,-179.9 87.06,-204.34 81.39,-222.56"/> +<polygon fill="black" stroke="black" points="78.01,-221.66 78.49,-232.24 84.71,-223.67 78.01,-221.66"/> +<text text-anchor="middle" x="130.12" y="-126.75" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return25->Region23 --> +<g id="edge26" class="edge"> +<title>Return25->Region23</title> +<path fill="none" stroke="red" d="M170.51,-124.3C181.46,-133.12 194.95,-143.97 206.82,-153.53"/> +<polygon fill="red" stroke="red" points="204.76,-156.35 214.74,-159.9 209.14,-150.9 204.76,-156.35"/> +<text text-anchor="middle" x="167.51" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Phi_224 --> +<g id="edge28" class="edge"> +<title>Return25->Phi_224</title> +<path fill="none" stroke="blue" d="M149,-124.3C149,-132.02 149,-141.29 149,-149.89"/> +<polygon fill="blue" stroke="blue" points="145.5,-149.9 149,-159.9 152.5,-149.9 145.5,-149.9"/> +<text text-anchor="middle" x="146" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph3-snip.jpg b/chapter11/docs/graph3-snip.jpg Binary files differnew file mode 100644 index 00000000..804a32c4 --- /dev/null +++ b/chapter11/docs/graph3-snip.jpg diff --git a/chapter11/docs/graph3.gv b/chapter11/docs/graph3.gv new file mode 100644 index 00000000..c47ac565 --- /dev/null +++ b/chapter11/docs/graph3.gv @@ -0,0 +1,81 @@ +digraph chapter11 { +/* +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Con_11 [ label="#2" ]; + Store12 [ label=".f =" ]; + new13 [ label="new S" ]; + Store14 [ label=".f =" ]; + Load16 [ label=".f" ]; + If17 [ shape=plaintext label=< + <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_21 [ label="#1" ]; + Store22 [ label=".f =" ]; + Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region23;Phi_224;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return25[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store12 -> Start2:p0[taillabel=0 color=red]; + Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> new9[taillabel=2]; + Store12 -> Con_11[taillabel=3]; + new13 -> Start2:p0[taillabel=0 color=green]; + Store14 -> Start2:p0[taillabel=0 color=red]; + Store14 -> Store12[taillabel=1 color=blue]; + Store14 -> new13[taillabel=2]; + Store14 -> Con_4[taillabel=3]; + Load16 -> Start2:p0[taillabel=0 color=red]; + Load16 -> Store14[taillabel=1 color=blue]; + Load16 -> new9[taillabel=2]; + If17 -> Start2:p0[taillabel=0 color=red]; + If17 -> Start2:p1[taillabel=1]; + Store22 -> If17:p0[taillabel=0 color=red]; + Store22 -> Store14[taillabel=1 color=blue]; + Store22 -> new9[taillabel=2]; + Store22 -> Con_21[taillabel=3]; + Store22 -> Load16[taillabel=4]; + Region23 -> If17:p0[taillabel=1 color=red]; + Region23 -> If17:p1[taillabel=2 color=red]; + Phi_224 -> Region23 [style=dotted taillabel=0]; + Phi_224 -> Store22[taillabel=1 color=blue]; + Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; + Return25 -> Region23[taillabel=0 color=red]; + Return25 -> Load16[taillabel=1]; + Return25 -> Phi_224[taillabel=2 color=blue]; +} diff --git a/chapter11/docs/graph3.svg b/chapter11/docs/graph3.svg new file mode 100644 index 00000000..e017c4af --- /dev/null +++ b/chapter11/docs/graph3.svg @@ -0,0 +1,316 @@ +<?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: chapter11 Pages: 1 --> +<svg width="346pt" height="708pt" + viewBox="0.00 0.00 346.00 708.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 704)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-704 342,-704 342,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-692 330,-692 330,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="137,-655 137,-680 243,-680 243,-655 137,-655"/> +<polygon fill="none" stroke="black" points="137,-655 137,-680 243,-680 243,-655 137,-655"/> +<text text-anchor="start" x="171.5" y="-663.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="137,-624 137,-655 243,-655 243,-624 137,-624"/> +<polygon fill="yellow" stroke="transparent" points="142,-629 142,-650 182,-650 182,-629 142,-629"/> +<polygon fill="none" stroke="black" points="142,-629 142,-650 182,-650 182,-629 142,-629"/> +<text text-anchor="start" x="145" y="-635.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="182,-629 182,-650 213,-650 213,-629 182,-629"/> +<text text-anchor="start" x="185" y="-635.8" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="213,-629 213,-650 238,-650 238,-629 213,-629"/> +<text text-anchor="start" x="216" y="-635.8" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="186,-52 132,-52 132,-16 186,-16 186,-52"/> +<text text-anchor="middle" x="159" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return25 --> +<g id="node15" class="node"> +<title>Return25</title> +<polygon fill="yellow" stroke="black" points="192,-124 126,-124 126,-88 192,-88 192,-124"/> +<text text-anchor="middle" x="159" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return25 --> +<g id="edge1" class="edge"> +<title>Stop3->Return25</title> +<path fill="none" stroke="red" d="M159,-52.3C159,-60.02 159,-69.29 159,-77.89"/> +<polygon fill="red" stroke="red" points="155.5,-77.9 159,-87.9 162.5,-77.9 155.5,-77.9"/> +<text text-anchor="middle" x="156" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="87" cy="-494" rx="27" ry="18"/> +<text text-anchor="middle" x="87" y="-490.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="191" cy="-566" rx="38.99" ry="18"/> +<text text-anchor="middle" x="191" y="-562.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M178.53,-583.11C172.27,-592.49 165.49,-605 162.99,-618"/> +<polygon fill="green" stroke="green" points="159.51,-617.7 162,-628 166.47,-618.4 159.51,-617.7"/> +<text text-anchor="middle" x="175.53" y="-585.71" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_11 --> +<g id="node5" class="node"> +<title>Con_11</title> +<ellipse fill="none" stroke="black" cx="275" cy="-566" rx="27" ry="18"/> +<text text-anchor="middle" x="275" y="-562.3" font-family="Times,serif" font-size="14.00">#2</text> +</g> +<!-- Store12 --> +<g id="node6" class="node"> +<title>Store12</title> +<ellipse fill="none" stroke="black" cx="256" cy="-494" rx="27.9" ry="18"/> +<text text-anchor="middle" x="256" y="-490.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store12->Start2 --> +<g id="edge3" class="edge"> +<title>Store12->Start2:p0</title> +<path fill="none" stroke="red" d="M278.09,-505.5C299.78,-517.5 328.59,-538.88 321,-565"/> +<path fill="none" stroke="red" d="M321,-567C301.92,-632.61 187.71,-567.79 165.68,-610.25"/> +<polygon fill="red" stroke="red" points="162.32,-609.27 162.62,-619.86 168.99,-611.39 162.32,-609.27"/> +<text text-anchor="middle" x="281.09" y="-508.1" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store12->new9 --> +<g id="edge5" class="edge"> +<title>Store12->new9</title> +<path fill="none" stroke="black" d="M242.22,-509.85C233.66,-519.06 222.49,-531.09 212.76,-541.57"/> +<polygon fill="black" stroke="black" points="209.91,-539.49 205.67,-549.2 215.04,-544.26 209.91,-539.49"/> +<text text-anchor="middle" x="239.22" y="-512.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store12->Con_11 --> +<g id="edge6" class="edge"> +<title>Store12->Con_11</title> +<path fill="none" stroke="black" d="M260.6,-511.95C262.75,-519.86 265.35,-529.46 267.76,-538.31"/> +<polygon fill="black" stroke="black" points="264.45,-539.48 270.44,-548.21 271.2,-537.64 264.45,-539.48"/> +<text text-anchor="middle" x="257.6" y="-514.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- new13 --> +<g id="node7" class="node"> +<title>new13</title> +<ellipse fill="none" stroke="black" cx="75" cy="-566" rx="38.99" ry="18"/> +<text text-anchor="middle" x="75" y="-562.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new13->Start2 --> +<g id="edge7" class="edge"> +<title>new13->Start2:p0</title> +<path fill="none" stroke="green" d="M81.73,-584.06C90.25,-602.86 106.79,-631.28 130.97,-637.69"/> +<polygon fill="green" stroke="green" points="130.63,-641.17 141,-639 131.54,-634.23 130.63,-641.17"/> +<text text-anchor="middle" x="78.73" y="-586.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store14 --> +<g id="node8" class="node"> +<title>Store14</title> +<ellipse fill="none" stroke="black" cx="133" cy="-422" rx="27.9" ry="18"/> +<text text-anchor="middle" x="133" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store14->Start2 --> +<g id="edge8" class="edge"> +<title>Store14->Start2:p0</title> +<path fill="none" stroke="red" d="M133,-495C125.43,-534.08 132.78,-545.53 143,-584 146.42,-596.87 152.99,-602.52 157.44,-610.16"/> +<polygon fill="red" stroke="red" points="154.26,-611.64 161.18,-619.66 160.77,-609.07 154.26,-611.64"/> +<text text-anchor="middle" x="130" y="-488.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store14->Con_4 --> +<g id="edge11" class="edge"> +<title>Store14->Con_4</title> +<path fill="none" stroke="black" d="M122.56,-438.88C116.8,-447.66 109.52,-458.74 103.04,-468.58"/> +<polygon fill="black" stroke="black" points="99.96,-466.9 97.4,-477.18 105.81,-470.74 99.96,-466.9"/> +<text text-anchor="middle" x="119.56" y="-441.48" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store14->Store12 --> +<g id="edge9" class="edge"> +<title>Store14->Store12</title> +<path fill="none" stroke="blue" d="M153.31,-434.56C173.34,-445.96 204.16,-463.5 226.81,-476.39"/> +<polygon fill="blue" stroke="blue" points="225.24,-479.52 235.66,-481.42 228.7,-473.44 225.24,-479.52"/> +<text text-anchor="middle" x="150.31" y="-437.16" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store14->new13 --> +<g id="edge10" class="edge"> +<title>Store14->new13</title> +<path fill="none" stroke="black" d="M134.81,-440.15C135.88,-454.43 136.45,-475.22 133,-493"/> +<path fill="none" stroke="black" d="M133,-495C129.28,-514.17 115.61,-531.16 102.48,-543.6"/> +<polygon fill="black" stroke="black" points="100.01,-541.11 94.89,-550.38 104.68,-546.33 100.01,-541.11"/> +<text text-anchor="middle" x="131.81" y="-442.75" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16 --> +<g id="node9" class="node"> +<title>Load16</title> +<ellipse fill="none" stroke="black" cx="53" cy="-336" rx="27" ry="18"/> +<text text-anchor="middle" x="53" y="-332.3" font-family="Times,serif" font-size="14.00">.f</text> +</g> +<!-- Load16->Start2 --> +<g id="edge12" class="edge"> +<title>Load16->Start2:p0</title> +<path fill="none" stroke="red" d="M47.25,-353.59C38.09,-382.82 22.94,-444.94 41,-493"/> +<path fill="none" stroke="red" d="M41,-495C50.66,-517.36 30.14,-523.84 27,-548 24.94,-563.87 18.49,-570.45 27,-584 52.69,-624.92 76.85,-636.12 118.8,-638.47"/> +<polygon fill="red" stroke="red" points="118.8,-641.97 128.93,-638.86 119.08,-634.98 118.8,-641.97"/> +<text text-anchor="middle" x="44.25" y="-356.19" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Load16->new9 --> +<g id="edge14" class="edge"> +<title>Load16->new9</title> +<path fill="none" stroke="black" d="M41,-495C44.08,-503.21 44.3,-506.35 51,-512 66.3,-524.9 113.87,-541.46 149.33,-552.61"/> +<polygon fill="black" stroke="black" points="148.39,-555.98 158.98,-555.6 150.47,-549.3 148.39,-555.98"/> +<text text-anchor="middle" x="38" y="-488.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Load16->Store14 --> +<g id="edge13" class="edge"> +<title>Load16->Store14</title> +<path fill="none" stroke="blue" d="M66.93,-351.63C79.36,-364.68 97.74,-383.97 111.95,-398.9"/> +<polygon fill="blue" stroke="blue" points="109.53,-401.43 118.96,-406.25 114.59,-396.6 109.53,-401.43"/> +<text text-anchor="middle" x="63.93" y="-354.23" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If17 --> +<g id="node10" class="node"> +<title>If17</title> +<polygon fill="yellow" stroke="transparent" points="218,-339 218,-364 312,-364 312,-339 218,-339"/> +<polygon fill="none" stroke="black" points="218,-339 218,-364 312,-364 312,-339 218,-339"/> +<text text-anchor="start" x="259.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="218,-308 218,-339 312,-339 312,-308 218,-308"/> +<polygon fill="yellow" stroke="transparent" points="223,-313 223,-334 263,-334 263,-313 223,-313"/> +<polygon fill="none" stroke="black" points="223,-313 223,-334 263,-334 263,-313 223,-313"/> +<text text-anchor="start" x="226" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="263,-313 263,-334 307,-334 307,-313 263,-313"/> +<polygon fill="none" stroke="black" points="263,-313 263,-334 307,-334 307,-313 263,-313"/> +<text text-anchor="start" x="266" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If17->Start2 --> +<g id="edge15" class="edge"> +<title>If17->Start2:p0</title> +<path fill="none" stroke="red" d="M283.07,-368.2C306.27,-412.32 341.25,-495.33 321,-565"/> +<text text-anchor="middle" x="280.07" y="-370.8" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_21 --> +<g id="node11" class="node"> +<title>Con_21</title> +<ellipse fill="none" stroke="black" cx="125" cy="-336" rx="27" ry="18"/> +<text text-anchor="middle" x="125" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Store22 --> +<g id="node12" class="node"> +<title>Store22</title> +<ellipse fill="none" stroke="black" cx="159" cy="-250" rx="27.9" ry="18"/> +<text text-anchor="middle" x="159" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store22->new9 --> +<g id="edge19" class="edge"> +<title>Store22->new9</title> +<path fill="none" stroke="black" d="M191,-337C192.12,-409.45 191.64,-494.88 191.27,-537.67"/> +<polygon fill="black" stroke="black" points="187.77,-537.81 191.18,-547.84 194.77,-537.87 187.77,-537.81"/> +<text text-anchor="middle" x="188" y="-330.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store22->Store14 --> +<g id="edge18" class="edge"> +<title>Store22->Store14</title> +<path fill="none" stroke="blue" d="M168.96,-267.05C178.21,-283.46 190.61,-310.03 191,-335"/> +<path fill="none" stroke="blue" d="M191,-337C191.39,-361.97 173.77,-384.91 157.85,-400.48"/> +<polygon fill="blue" stroke="blue" points="155.22,-398.15 150.26,-407.51 159.98,-403.28 155.22,-398.15"/> +<text text-anchor="middle" x="165.96" y="-269.65" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store22->Load16 --> +<g id="edge21" class="edge"> +<title>Store22->Load16</title> +<path fill="none" stroke="black" d="M141.22,-263.98C126.92,-274.53 106.43,-289.92 89,-304 84.86,-307.35 80.51,-310.98 76.33,-314.54"/> +<polygon fill="black" stroke="black" points="73.89,-312.03 68.59,-321.2 78.45,-317.33 73.89,-312.03"/> +<text text-anchor="middle" x="138.22" y="-266.58" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> +</g> +<!-- Store22->If17 --> +<g id="edge17" class="edge"> +<title>Store22->If17:p0</title> +<path fill="none" stroke="red" d="M170.65,-266.62C182.83,-282.67 201.77,-306.77 213.47,-317.51"/> +<polygon fill="red" stroke="red" points="211.7,-320.53 222,-323 215.49,-314.64 211.7,-320.53"/> +<text text-anchor="middle" x="173.65" y="-269.22" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store22->Con_21 --> +<g id="edge20" class="edge"> +<title>Store22->Con_21</title> +<path fill="none" stroke="black" d="M152.28,-267.6C147.49,-279.45 140.94,-295.62 135.46,-309.15"/> +<polygon fill="black" stroke="black" points="132.21,-307.86 131.7,-318.44 138.7,-310.48 132.21,-307.86"/> +<text text-anchor="middle" x="149.28" y="-270.2" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Region23 --> +<g id="node13" class="node"> +<title>Region23</title> +<polygon fill="yellow" stroke="black" points="276,-196 210,-196 210,-160 276,-160 276,-196"/> +<text text-anchor="middle" x="243" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Region23->If17 --> +<g id="edge22" class="edge"> +<title>Region23->If17:p0</title> +<path fill="none" stroke="red" d="M243,-196.22C243,-219.49 243,-262.58 243,-301.64"/> +<polygon fill="red" stroke="red" points="239.5,-302 243,-312 246.5,-302 239.5,-302"/> +<text text-anchor="middle" x="240" y="-198.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region23->If17 --> +<g id="edge23" class="edge"> +<title>Region23->If17:p1</title> +<path fill="none" stroke="red" d="M252.84,-196.18C258.29,-206.33 264.79,-219.58 269,-232 279.46,-262.85 284.04,-273.57 284.86,-301.73"/> +<polygon fill="red" stroke="red" points="281.37,-302.05 285,-312 288.37,-301.95 281.37,-302.05"/> +<text text-anchor="middle" x="249.84" y="-198.78" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224 --> +<g id="node14" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="159" cy="-178" rx="33.29" ry="18"/> +<text text-anchor="middle" x="159" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_224->Store14 --> +<g id="edge26" class="edge"> +<title>Phi_224->Store14</title> +<path fill="none" stroke="blue" d="M173.5,-194.31C181.86,-204.22 191.56,-217.87 196,-232 200.8,-247.26 198.68,-252.23 196,-268 192.06,-291.21 170.41,-346.42 161,-368 156.97,-377.25 151.97,-387.09 147.32,-395.75"/> +<polygon fill="blue" stroke="blue" points="144.21,-394.15 142.48,-404.6 150.35,-397.51 144.21,-394.15"/> +<text text-anchor="middle" x="170.5" y="-196.91" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_224->Store22 --> +<g id="edge25" class="edge"> +<title>Phi_224->Store22</title> +<path fill="none" stroke="blue" d="M159,-196.3C159,-204.02 159,-213.29 159,-221.89"/> +<polygon fill="blue" stroke="blue" points="155.5,-221.9 159,-231.9 162.5,-221.9 155.5,-221.9"/> +<text text-anchor="middle" x="156" y="-198.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_224->Region23 --> +<g id="edge24" class="edge"> +<title>Phi_224->Region23</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M192.47,-178C194.91,-178 197.35,-178 199.8,-178"/> +<polygon fill="black" stroke="black" points="199.84,-181.5 209.84,-178 199.84,-174.5 199.84,-181.5"/> +<text text-anchor="middle" x="195.47" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Load16 --> +<g id="edge28" class="edge"> +<title>Return25->Load16</title> +<path fill="none" stroke="black" d="M143.1,-124.14C134.51,-134.05 124.23,-147.09 117,-160 89.5,-209.12 69.82,-272.31 60.03,-307.76"/> +<polygon fill="black" stroke="black" points="56.54,-307.25 57.31,-317.82 63.3,-309.08 56.54,-307.25"/> +<text text-anchor="middle" x="140.1" y="-126.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return25->Region23 --> +<g id="edge27" class="edge"> +<title>Return25->Region23</title> +<path fill="none" stroke="red" d="M179.76,-124.3C190.24,-133.03 203.11,-143.76 214.5,-153.25"/> +<polygon fill="red" stroke="red" points="212.55,-156.18 222.47,-159.9 217.03,-150.81 212.55,-156.18"/> +<text text-anchor="middle" x="176.76" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->Phi_224 --> +<g id="edge29" class="edge"> +<title>Return25->Phi_224</title> +<path fill="none" stroke="blue" d="M159,-124.3C159,-132.02 159,-141.29 159,-149.89"/> +<polygon fill="blue" stroke="blue" points="155.5,-149.9 159,-159.9 162.5,-149.9 155.5,-149.9"/> +<text text-anchor="middle" x="156" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph4.gv b/chapter11/docs/graph4.gv new file mode 100644 index 00000000..190e83dd --- /dev/null +++ b/chapter11/docs/graph4.gv @@ -0,0 +1,107 @@ +digraph chapter11 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + <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>> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ 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>> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2:p1[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=1]; + Return37 -> Phi_230[taillabel=2 color=blue]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph4.svg b/chapter11/docs/graph4.svg new file mode 100644 index 00000000..7f392664 --- /dev/null +++ b/chapter11/docs/graph4.svg @@ -0,0 +1,408 @@ +<?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: chapter11 Pages: 1 --> +<svg width="454pt" height="680pt" + viewBox="0.00 0.00 454.00 680.14" 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 676.14)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-676.14 450,-676.14 450,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-24.14 8,-664.14 438,-664.14 438,-24.14 8,-24.14"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="129,-627.14 129,-652.14 235,-652.14 235,-627.14 129,-627.14"/> +<polygon fill="none" stroke="black" points="129,-627.14 129,-652.14 235,-652.14 235,-627.14 129,-627.14"/> +<text text-anchor="start" x="163.5" y="-635.94" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="129,-596.14 129,-627.14 235,-627.14 235,-596.14 129,-596.14"/> +<polygon fill="yellow" stroke="transparent" points="134,-601.14 134,-622.14 174,-622.14 174,-601.14 134,-601.14"/> +<polygon fill="none" stroke="black" points="134,-601.14 134,-622.14 174,-622.14 174,-601.14 134,-601.14"/> +<text text-anchor="start" x="137" y="-607.94" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="174,-601.14 174,-622.14 205,-622.14 205,-601.14 174,-601.14"/> +<text text-anchor="start" x="177" y="-607.94" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="205,-601.14 205,-622.14 230,-622.14 230,-601.14 205,-601.14"/> +<text text-anchor="start" x="208" y="-607.94" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="189,-68.14 135,-68.14 135,-32.14 189,-32.14 189,-68.14"/> +<text text-anchor="middle" x="162" y="-46.44" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return37 --> +<g id="node21" class="node"> +<title>Return37</title> +<polygon fill="yellow" stroke="black" points="193,-154.14 127,-154.14 127,-118.14 193,-118.14 193,-154.14"/> +<text text-anchor="middle" x="160" y="-132.44" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return37 --> +<g id="edge1" class="edge"> +<title>Stop3->Return37</title> +<path fill="none" stroke="red" d="M161.59,-68.55C161.32,-79.89 160.96,-94.92 160.65,-107.83"/> +<polygon fill="red" stroke="red" points="157.15,-107.97 160.41,-118.05 164.14,-108.13 157.15,-107.97"/> +<text text-anchor="middle" x="158.59" y="-71.15" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="264" cy="-538.14" rx="27" ry="18"/> +<text text-anchor="middle" x="264" y="-534.44" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="55" cy="-538.14" rx="38.99" ry="18"/> +<text text-anchor="middle" x="55" y="-534.44" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M63.59,-555.84C74.57,-574.87 95.49,-604.11 123,-610.06"/> +<polygon fill="green" stroke="green" points="122.68,-613.55 133,-611.14 123.43,-606.59 122.68,-613.55"/> +<text text-anchor="middle" x="60.59" y="-558.44" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store10 --> +<g id="node5" class="node"> +<title>Store10</title> +<ellipse fill="none" stroke="black" cx="218" cy="-466.14" rx="27.9" ry="18"/> +<text text-anchor="middle" x="218" y="-462.44" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store10->Start2 --> +<g id="edge3" class="edge"> +<title>Store10->Start2:p2</title> +<path fill="none" stroke="blue" d="M218,-484.36C218,-507.63 218,-550.72 218,-589.78"/> +<polygon fill="blue" stroke="blue" points="214.5,-590.14 218,-600.14 221.5,-590.14 214.5,-590.14"/> +<text text-anchor="middle" x="215" y="-486.96" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store10->Con_4 --> +<g id="edge5" class="edge"> +<title>Store10->Con_4</title> +<path fill="none" stroke="black" d="M228.44,-483.03C234.2,-491.8 241.48,-502.88 247.96,-512.73"/> +<polygon fill="black" stroke="black" points="245.19,-514.89 253.6,-521.32 251.04,-511.04 245.19,-514.89"/> +<text text-anchor="middle" x="225.44" y="-485.63" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store10->new9 --> +<g id="edge4" class="edge"> +<title>Store10->new9</title> +<path fill="none" stroke="black" d="M195.75,-477.51C190.9,-479.73 185.79,-482.04 181,-484.14 151.47,-497.13 117.77,-511.27 92.65,-521.68"/> +<polygon fill="black" stroke="black" points="91.04,-518.56 83.14,-525.61 93.72,-525.03 91.04,-518.56"/> +<text text-anchor="middle" x="192.75" y="-480.11" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Loop11 --> +<g id="node6" class="node"> +<title>Loop11</title> +<polygon fill="yellow" stroke="black" points="430,-412.14 376,-412.14 376,-376.14 430,-376.14 430,-412.14"/> +<text text-anchor="middle" x="403" y="-390.44" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop11->Start2 --> +<g id="edge6" class="edge"> +<title>Loop11->Start2:p0</title> +<path fill="none" stroke="red" d="M398.43,-412.15C388.69,-445.12 362.68,-517.81 314,-556.14 259,-599.46 164.25,-533.74 154.77,-590.03"/> +<polygon fill="red" stroke="red" points="151.27,-589.91 154,-600.14 158.25,-590.44 151.27,-589.91"/> +<text text-anchor="middle" x="395.43" y="-414.75" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32 --> +<g id="node17" class="node"> +<title>Region32</title> +<polygon fill="yellow" stroke="black" points="278,-68.14 212,-68.14 212,-32.14 278,-32.14 278,-68.14"/> +<text text-anchor="middle" x="245" y="-46.44" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Loop11->Region32 --> +<g id="edge7" class="edge"> +<title>Loop11->Region32</title> +<path fill="none" stroke="red" d="M375.77,-379.43C372.85,-378.21 369.89,-377.09 367,-376.14 287.4,-350.06 245.88,-394.32 182,-340.14 148.1,-311.39 72.96,-171.74 118,-104.14 120.57,-100.29 166.66,-81.64 202.59,-67.55"/> +<polygon fill="red" stroke="red" points="203.9,-70.79 211.94,-63.89 201.35,-64.27 203.9,-70.79"/> +<text text-anchor="middle" x="372.77" y="-373.03" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_arg13 --> +<g id="node7" class="node"> +<title>Phi_arg13</title> +<ellipse fill="lightyellow" stroke="black" cx="321" cy="-394.14" rx="37.09" ry="18"/> +<text text-anchor="middle" x="321" y="-390.44" font-family="Times,serif" font-size="14.00">φ_arg</text> +</g> +<!-- Phi_arg13->Start2 --> +<g id="edge9" class="edge"> +<title>Phi_arg13->Start2:p1</title> +<path fill="none" stroke="black" d="M323.94,-412.28C328.4,-444.2 333.18,-513.39 300,-556.14 270.11,-594.65 201.29,-554.44 191.23,-590.04"/> +<polygon fill="black" stroke="black" points="187.74,-589.79 190,-600.14 194.69,-590.64 187.74,-589.79"/> +<text text-anchor="middle" x="320.94" y="-414.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_arg13->Loop11 --> +<g id="edge8" class="edge"> +<title>Phi_arg13->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M358.16,-394.14C360.62,-394.14 363.08,-394.14 365.55,-394.14"/> +<polygon fill="black" stroke="black" points="365.67,-397.64 375.67,-394.14 365.67,-390.64 365.67,-397.64"/> +<text text-anchor="middle" x="361.16" y="-387.74" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35 --> +<g id="node19" class="node"> +<title>Sub35</title> +<ellipse fill="none" stroke="black" cx="218" cy="-322.14" rx="27" ry="18"/> +<text text-anchor="middle" x="218" y="-318.44" font-family="Times,serif" font-size="14.00">-</text> +</g> +<!-- LT14 --> +<g id="node8" class="node"> +<title>LT14</title> +<ellipse fill="none" stroke="black" cx="290" cy="-322.14" rx="27" ry="18"/> +<text text-anchor="middle" x="290" y="-318.44" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT14->Con_4 --> +<g id="edge11" class="edge"> +<title>LT14->Con_4</title> +<path fill="none" stroke="black" d="M284.22,-340.12C280.96,-350.42 277.13,-363.89 275,-376.14 266.94,-422.56 264.72,-477.47 264.15,-509.69"/> +<polygon fill="black" stroke="black" points="260.64,-510.04 264.01,-520.08 267.64,-510.13 260.64,-510.04"/> +<text text-anchor="middle" x="281.22" y="-342.72" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- LT14->Phi_arg13 --> +<g id="edge12" class="edge"> +<title>LT14->Phi_arg13</title> +<path fill="none" stroke="black" d="M297.35,-339.73C300.92,-347.81 305.31,-357.71 309.33,-366.79"/> +<polygon fill="black" stroke="black" points="306.24,-368.46 313.49,-376.19 312.64,-365.63 306.24,-368.46"/> +<text text-anchor="middle" x="294.35" y="-342.33" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If16 --> +<g id="node9" class="node"> +<title>If16</title> +<polygon fill="yellow" stroke="transparent" points="242,-239.14 242,-264.14 336,-264.14 336,-239.14 242,-239.14"/> +<polygon fill="none" stroke="black" points="242,-239.14 242,-264.14 336,-264.14 336,-239.14 242,-239.14"/> +<text text-anchor="start" x="283.5" y="-247.94" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="242,-208.14 242,-239.14 336,-239.14 336,-208.14 242,-208.14"/> +<polygon fill="yellow" stroke="transparent" points="247,-213.14 247,-234.14 287,-234.14 287,-213.14 247,-213.14"/> +<polygon fill="none" stroke="black" points="247,-213.14 247,-234.14 287,-234.14 287,-213.14 247,-213.14"/> +<text text-anchor="start" x="250" y="-219.94" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="287,-213.14 287,-234.14 331,-234.14 331,-213.14 287,-213.14"/> +<polygon fill="none" stroke="black" points="287,-213.14 287,-234.14 331,-234.14 331,-213.14 287,-213.14"/> +<text text-anchor="start" x="290" y="-219.94" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If16->Loop11 --> +<g id="edge13" class="edge"> +<title>If16->Loop11</title> +<path fill="none" stroke="red" d="M311.8,-268.34C333.14,-297.54 364.63,-340.64 384.47,-367.78"/> +<polygon fill="red" stroke="red" points="381.75,-369.99 390.47,-376 387.4,-365.86 381.75,-369.99"/> +<text text-anchor="middle" x="308.8" y="-270.94" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If16->LT14 --> +<g id="edge14" class="edge"> +<title>If16->LT14</title> +<path fill="none" stroke="black" d="M289.37,-268.42C289.47,-276.72 289.58,-285.63 289.67,-293.71"/> +<polygon fill="black" stroke="black" points="286.17,-293.78 289.79,-303.74 293.17,-293.7 286.17,-293.78"/> +<text text-anchor="middle" x="286.37" y="-271.02" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Div21 --> +<g id="node10" class="node"> +<title>Div21</title> +<ellipse fill="none" stroke="black" cx="63" cy="-394.14" rx="27" ry="18"/> +<text text-anchor="middle" x="63" y="-390.44" font-family="Times,serif" font-size="14.00">//</text> +</g> +<!-- Div21->Start2 --> +<g id="edge15" class="edge"> +<title>Div21->Start2:p1</title> +<path fill="none" stroke="black" d="M70.68,-411.57C79.34,-429.71 94.16,-459.58 109,-484.14 139.67,-534.92 184.56,-537.17 189.54,-590.05"/> +<polygon fill="black" stroke="black" points="186.05,-590.31 190,-600.14 193.05,-590 186.05,-590.31"/> +<text text-anchor="middle" x="67.68" y="-414.17" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_22 --> +<g id="node11" class="node"> +<title>Con_22</title> +<ellipse fill="none" stroke="black" cx="145" cy="-466.14" rx="27" ry="18"/> +<text text-anchor="middle" x="145" y="-462.44" font-family="Times,serif" font-size="14.00">#3</text> +</g> +<!-- Div21->Con_22 --> +<g id="edge16" class="edge"> +<title>Div21->Con_22</title> +<path fill="none" stroke="black" d="M79.2,-408.97C91.18,-419.2 107.69,-433.29 121.18,-444.8"/> +<polygon fill="black" stroke="black" points="119.23,-447.75 129.11,-451.58 123.78,-442.43 119.23,-447.75"/> +<text text-anchor="middle" x="76.2" y="-411.57" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- EQ23 --> +<g id="node12" class="node"> +<title>EQ23</title> +<ellipse fill="none" stroke="black" cx="389" cy="-236.14" rx="27" ry="18"/> +<text text-anchor="middle" x="389" y="-232.44" font-family="Times,serif" font-size="14.00">==</text> +</g> +<!-- EQ23->Phi_arg13 --> +<g id="edge17" class="edge"> +<title>EQ23->Phi_arg13</title> +<path fill="none" stroke="black" d="M381.85,-253.54C370.11,-280.48 346.63,-334.34 332.52,-366.71"/> +<polygon fill="black" stroke="black" points="329.25,-365.45 328.46,-376.02 335.67,-368.25 329.25,-365.45"/> +<text text-anchor="middle" x="378.85" y="-256.14" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_24 --> +<g id="node13" class="node"> +<title>Con_24</title> +<ellipse fill="none" stroke="black" cx="402" cy="-322.14" rx="27" ry="18"/> +<text text-anchor="middle" x="402" y="-318.44" font-family="Times,serif" font-size="14.00">#5</text> +</g> +<!-- EQ23->Con_24 --> +<g id="edge18" class="edge"> +<title>EQ23->Con_24</title> +<path fill="none" stroke="black" d="M391.63,-254.14C393.41,-265.64 395.8,-281.07 397.83,-294.23"/> +<polygon fill="black" stroke="black" points="394.38,-294.82 399.37,-304.17 401.3,-293.75 394.38,-294.82"/> +<text text-anchor="middle" x="388.63" y="-256.74" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If25 --> +<g id="node14" class="node"> +<title>If25</title> +<polygon fill="yellow" stroke="transparent" points="220,-139.14 220,-164.14 314,-164.14 314,-139.14 220,-139.14"/> +<polygon fill="none" stroke="black" points="220,-139.14 220,-164.14 314,-164.14 314,-139.14 220,-139.14"/> +<text text-anchor="start" x="261.5" y="-147.94" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="220,-108.14 220,-139.14 314,-139.14 314,-108.14 220,-108.14"/> +<polygon fill="yellow" stroke="transparent" points="225,-113.14 225,-134.14 265,-134.14 265,-113.14 225,-113.14"/> +<polygon fill="none" stroke="black" points="225,-113.14 225,-134.14 265,-134.14 265,-113.14 225,-113.14"/> +<text text-anchor="start" x="228" y="-119.94" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="265,-113.14 265,-134.14 309,-134.14 309,-113.14 265,-113.14"/> +<polygon fill="none" stroke="black" points="265,-113.14 265,-134.14 309,-134.14 309,-113.14 265,-113.14"/> +<text text-anchor="start" x="268" y="-119.94" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If25->If16 --> +<g id="edge19" class="edge"> +<title>If25->If16:p0</title> +<path fill="none" stroke="red" d="M267,-168.16C267,-178.61 267,-190.56 267,-202.13"/> +<polygon fill="red" stroke="red" points="263.5,-202.14 267,-212.14 270.5,-202.14 263.5,-202.14"/> +<text text-anchor="middle" x="264" y="-170.76" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If25->EQ23 --> +<g id="edge20" class="edge"> +<title>If25->EQ23</title> +<path fill="none" stroke="black" d="M308.01,-168.43C322.36,-179.56 338.51,-192.27 353,-204.14 357.12,-207.52 361.45,-211.17 365.63,-214.73"/> +<polygon fill="black" stroke="black" points="363.51,-217.52 373.37,-221.39 368.08,-212.22 363.51,-217.52"/> +<text text-anchor="middle" x="305.01" y="-171.03" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230 --> +<g id="node15" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="141" cy="-394.14" rx="33.29" ry="18"/> +<text text-anchor="middle" x="141" y="-390.44" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_230->Store10 --> +<g id="edge22" class="edge"> +<title>Phi_230->Store10</title> +<path fill="none" stroke="blue" d="M157.33,-409.99C168.27,-419.93 182.82,-433.16 194.94,-444.18"/> +<polygon fill="blue" stroke="blue" points="192.73,-446.9 202.48,-451.04 197.44,-441.72 192.73,-446.9"/> +<text text-anchor="middle" x="154.33" y="-412.59" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230->Loop11 --> +<g id="edge21" class="edge"> +<title>Phi_230->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M157.93,-378.17C167.22,-370.51 179.4,-362.14 192,-358.14 262.32,-335.82 288.38,-333.72 358,-358.14 365.14,-360.65 371.99,-364.81 378.09,-369.44"/> +<polygon fill="black" stroke="black" points="375.99,-372.24 385.92,-375.93 380.45,-366.85 375.99,-372.24"/> +<text text-anchor="middle" x="154.93" y="-371.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_233 --> +<g id="node18" class="node"> +<title>Phi_233</title> +<ellipse fill="lightyellow" stroke="black" cx="84" cy="-50.14" rx="33.29" ry="18"/> +<text text-anchor="middle" x="84" y="-46.44" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Store31 --> +<g id="node16" class="node"> +<title>Store31</title> +<ellipse fill="none" stroke="black" cx="84" cy="-322.14" rx="27.9" ry="18"/> +<text text-anchor="middle" x="84" y="-318.44" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store31->new9 --> +<g id="edge25" class="edge"> +<title>Store31->new9</title> +<path fill="none" stroke="black" d="M64.25,-334.88C46.61,-346.87 22.71,-367.36 17,-393.14"/> +<text text-anchor="middle" x="61.25" y="-337.48" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store31->Div21 --> +<g id="edge26" class="edge"> +<title>Store31->Div21</title> +<path fill="none" stroke="black" d="M78.92,-340.09C76.52,-348.08 73.6,-357.81 70.92,-366.74"/> +<polygon fill="black" stroke="black" points="67.56,-365.77 68.04,-376.35 74.26,-367.78 67.56,-365.77"/> +<text text-anchor="middle" x="75.92" y="-342.69" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store31->Phi_230 --> +<g id="edge24" class="edge"> +<title>Store31->Phi_230</title> +<path fill="none" stroke="blue" d="M96.37,-338.33C103.81,-347.47 113.44,-359.29 121.84,-369.62"/> +<polygon fill="blue" stroke="blue" points="119.23,-371.95 128.26,-377.5 124.66,-367.53 119.23,-371.95"/> +<text text-anchor="middle" x="93.37" y="-340.93" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge27" class="edge"> +<title>Region32->If25:p0</title> +<path fill="none" stroke="red" d="M245,-68.23C245,-77.69 245,-89.92 245,-101.83"/> +<polygon fill="red" stroke="red" points="241.5,-102.14 245,-112.14 248.5,-102.14 241.5,-102.14"/> +<text text-anchor="middle" x="242" y="-70.83" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge28" class="edge"> +<title>Region32->If25:p1</title> +<path fill="none" stroke="red" d="M265.02,-68.27C273.64,-77.2 282.48,-88.98 285.71,-101.99"/> +<polygon fill="red" stroke="red" points="282.27,-102.66 287,-112.14 289.22,-101.78 282.27,-102.66"/> +<text text-anchor="middle" x="262.02" y="-70.87" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_233->Phi_230 --> +<g id="edge31" class="edge"> +<title>Phi_233->Phi_230</title> +<path fill="none" stroke="blue" d="M70.91,-76.61C45.62,-129.65 -3.2,-254.21 47,-340.14 53.76,-351.71 82.3,-366.89 106.18,-378.04"/> +<polygon fill="blue" stroke="blue" points="74.08,-78.08 75.32,-67.56 67.79,-75.01 74.08,-78.08"/> +<polygon fill="blue" stroke="blue" points="104.83,-381.28 115.38,-382.25 107.74,-374.91 104.83,-381.28"/> +<text text-anchor="middle" x="72.32" y="-70.16" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_233->Store31 --> +<g id="edge30" class="edge"> +<title>Phi_233->Store31</title> +<path fill="none" stroke="blue" d="M84,-68.44C84,-114.16 84,-238.47 84,-293.87"/> +<polygon fill="blue" stroke="blue" points="80.5,-293.99 84,-303.99 87.5,-293.99 80.5,-293.99"/> +<text text-anchor="middle" x="81" y="-71.04" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_233->Region32 --> +<g id="edge29" class="edge"> +<title>Phi_233->Region32</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M97.7,-33.41C107.03,-23.4 120.31,-11.6 135,-6.14 157.5,2.22 166.32,1.7 189,-6.14 200.57,-10.14 211.56,-17.63 220.7,-25.31"/> +<polygon fill="black" stroke="black" points="218.43,-27.97 228.22,-32.02 223.09,-22.75 218.43,-27.97"/> +<text text-anchor="middle" x="94.7" y="-27.01" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35->Phi_arg13 --> +<g id="edge32" class="edge"> +<title>Sub35->Phi_arg13</title> +<path fill="none" stroke="black" d="M244.96,-341.46C259.41,-351.29 277.23,-363.39 291.98,-373.42"/> +<polygon fill="black" stroke="black" points="246.65,-338.38 236.41,-335.66 242.72,-344.17 246.65,-338.38"/> +<polygon fill="black" stroke="black" points="290.03,-376.33 300.27,-379.05 293.96,-370.54 290.03,-376.33"/> +<text text-anchor="middle" x="233.41" y="-338.26" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_36 --> +<g id="node20" class="node"> +<title>Con_36</title> +<ellipse fill="none" stroke="black" cx="219" cy="-394.14" rx="27" ry="18"/> +<text text-anchor="middle" x="219" y="-390.44" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Sub35->Con_36 --> +<g id="edge33" class="edge"> +<title>Sub35->Con_36</title> +<path fill="none" stroke="black" d="M218.25,-340.45C218.36,-348.16 218.49,-357.43 218.61,-366.03"/> +<polygon fill="black" stroke="black" points="215.11,-366.09 218.76,-376.04 222.11,-365.99 215.11,-366.09"/> +<text text-anchor="middle" x="215.25" y="-343.05" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->new9 --> +<g id="edge35" class="edge"> +<title>Return37->new9</title> +<path fill="none" stroke="black" d="M146.13,-154.21C122.96,-183.41 76.06,-245.48 47,-304.14 28.47,-341.55 26.02,-352.39 17,-393.14"/> +<path fill="none" stroke="black" d="M17,-395.14C5.47,-435.72 24.68,-483.11 39.73,-511.59"/> +<polygon fill="black" stroke="black" points="36.74,-513.41 44.63,-520.48 42.87,-510.03 36.74,-513.41"/> +<text text-anchor="middle" x="143.13" y="-156.81" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return37->If16 --> +<g id="edge34" class="edge"> +<title>Return37->If16:p1</title> +<path fill="none" stroke="red" d="M181.67,-154.25C188.31,-159.08 195.77,-164.11 203,-168.14 244.24,-191.16 299.97,-165.55 308.02,-202.1"/> +<polygon fill="red" stroke="red" points="304.54,-202.53 309,-212.14 311.51,-201.85 304.54,-202.53"/> +<text text-anchor="middle" x="178.67" y="-156.85" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return37->Phi_230 --> +<g id="edge36" class="edge"> +<title>Return37->Phi_230</title> +<path fill="none" stroke="blue" d="M158.72,-154.34C155.49,-197.97 146.93,-313.25 143.01,-366.05"/> +<polygon fill="blue" stroke="blue" points="139.52,-365.79 142.27,-376.02 146.5,-366.31 139.52,-365.79"/> +<text text-anchor="middle" x="155.72" y="-156.94" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph5.gv b/chapter11/docs/graph5.gv new file mode 100644 index 00000000..9ebc11cb --- /dev/null +++ b/chapter11/docs/graph5.gv @@ -0,0 +1,113 @@ +digraph chapter11 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + <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>> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ 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>> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p0[taillabel=0 color=red]; + Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Loop11[taillabel=0 color=red]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2[taillabel=0 color=red]; + Div21 -> Start2:p1[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> Loop11[taillabel=0 color=red]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> Loop11[taillabel=0 color=red]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Loop11[taillabel=0 color=red]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=1]; + Return37 -> Phi_230[taillabel=2 color=blue]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph5.svg b/chapter11/docs/graph5.svg new file mode 100644 index 00000000..6dbf29f9 --- /dev/null +++ b/chapter11/docs/graph5.svg @@ -0,0 +1,452 @@ +<?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: chapter11 Pages: 1 --> +<svg width="474pt" height="680pt" + viewBox="0.00 0.00 474.00 679.67" 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 675.67)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-675.67 470,-675.67 470,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-23.67 8,-663.67 458,-663.67 458,-23.67 8,-23.67"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="109,-626.67 109,-651.67 215,-651.67 215,-626.67 109,-626.67"/> +<polygon fill="none" stroke="black" points="109,-626.67 109,-651.67 215,-651.67 215,-626.67 109,-626.67"/> +<text text-anchor="start" x="143.5" y="-635.47" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="109,-595.67 109,-626.67 215,-626.67 215,-595.67 109,-595.67"/> +<polygon fill="yellow" stroke="transparent" points="114,-600.67 114,-621.67 154,-621.67 154,-600.67 114,-600.67"/> +<polygon fill="none" stroke="black" points="114,-600.67 114,-621.67 154,-621.67 154,-600.67 114,-600.67"/> +<text text-anchor="start" x="117" y="-607.47" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="154,-600.67 154,-621.67 185,-621.67 185,-600.67 154,-600.67"/> +<text text-anchor="start" x="157" y="-607.47" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="185,-600.67 185,-621.67 210,-621.67 210,-600.67 185,-600.67"/> +<text text-anchor="start" x="188" y="-607.47" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="178,-67.67 124,-67.67 124,-31.67 178,-31.67 178,-67.67"/> +<text text-anchor="middle" x="151" y="-45.97" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return37 --> +<g id="node21" class="node"> +<title>Return37</title> +<polygon fill="yellow" stroke="black" points="184,-153.67 118,-153.67 118,-117.67 184,-117.67 184,-153.67"/> +<text text-anchor="middle" x="151" y="-131.97" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return37 --> +<g id="edge1" class="edge"> +<title>Stop3->Return37</title> +<path fill="none" stroke="red" d="M151,-68.08C151,-79.42 151,-94.45 151,-107.36"/> +<polygon fill="red" stroke="red" points="147.5,-107.58 151,-117.58 154.5,-107.58 147.5,-107.58"/> +<text text-anchor="middle" x="148" y="-70.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="262" cy="-537.67" rx="27" ry="18"/> +<text text-anchor="middle" x="262" y="-533.97" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="127" cy="-537.67" rx="38.99" ry="18"/> +<text text-anchor="middle" x="127" y="-533.97" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M130.01,-555.71C131.49,-565.24 133.09,-577.6 133.72,-589.67"/> +<polygon fill="green" stroke="green" points="130.22,-589.78 134,-599.67 137.22,-589.58 130.22,-589.78"/> +<text text-anchor="middle" x="127.01" y="-558.31" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store10 --> +<g id="node5" class="node"> +<title>Store10</title> +<ellipse fill="none" stroke="black" cx="138" cy="-465.67" rx="27.9" ry="18"/> +<text text-anchor="middle" x="138" y="-461.97" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store10->Start2 --> +<g id="edge3" class="edge"> +<title>Store10->Start2:p0</title> +<path fill="none" stroke="red" d="M154.15,-480.42C167.51,-493.39 184.39,-514.14 185,-536.67"/> +<path fill="none" stroke="red" d="M185,-538.67C185.75,-566.42 153.85,-566.41 140.14,-582.26"/> +<polygon fill="red" stroke="red" points="136.81,-581.05 135.08,-591.5 142.95,-584.41 136.81,-581.05"/> +<text text-anchor="middle" x="151.15" y="-483.02" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store10->Start2 --> +<g id="edge4" class="edge"> +<title>Store10->Start2:p2</title> +<path fill="none" stroke="blue" d="M155.9,-479.6C167.73,-489.22 182.4,-503.45 190,-519.67 203.61,-548.75 199.22,-561.31 198.19,-589.48"/> +<polygon fill="blue" stroke="blue" points="194.69,-589.61 198,-599.67 201.69,-589.74 194.69,-589.61"/> +<text text-anchor="middle" x="158.9" y="-482.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Store10->Con_4 --> +<g id="edge6" class="edge"> +<title>Store10->Con_4</title> +<path fill="none" stroke="black" d="M158.48,-478.23C178.79,-489.7 210.11,-507.38 232.98,-520.29"/> +<polygon fill="black" stroke="black" points="231.48,-523.46 241.9,-525.33 234.92,-517.37 231.48,-523.46"/> +<text text-anchor="middle" x="155.48" y="-471.83" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store10->new9 --> +<g id="edge5" class="edge"> +<title>Store10->new9</title> +<path fill="none" stroke="black" d="M135.34,-483.62C134.13,-491.33 132.66,-500.65 131.3,-509.31"/> +<polygon fill="black" stroke="black" points="127.81,-508.98 129.71,-519.4 134.72,-510.06 127.81,-508.98"/> +<text text-anchor="middle" x="132.34" y="-486.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Loop11 --> +<g id="node6" class="node"> +<title>Loop11</title> +<polygon fill="yellow" stroke="black" points="194,-411.67 140,-411.67 140,-375.67 194,-375.67 194,-411.67"/> +<text text-anchor="middle" x="167" y="-389.97" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop11->Start2 --> +<g id="edge7" class="edge"> +<title>Loop11->Start2:p0</title> +<path fill="none" stroke="red" d="M170.34,-412.08C175.19,-438.77 183.78,-491.46 185,-536.67"/> +<text text-anchor="middle" x="167.34" y="-414.68" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32 --> +<g id="node17" class="node"> +<title>Region32</title> +<polygon fill="yellow" stroke="black" points="268,-67.67 202,-67.67 202,-31.67 268,-31.67 268,-67.67"/> +<text text-anchor="middle" x="235" y="-45.97" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Loop11->Region32 --> +<g id="edge8" class="edge"> +<title>Loop11->Region32</title> +<path fill="none" stroke="red" d="M155.44,-375.36C125.2,-327.71 50.11,-192.34 109,-103.67 114.01,-96.13 157.85,-78.74 192.42,-65.93"/> +<polygon fill="red" stroke="red" points="193.71,-69.18 201.89,-62.45 191.3,-62.61 193.71,-69.18"/> +<text text-anchor="middle" x="152.44" y="-368.96" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_arg13 --> +<g id="node7" class="node"> +<title>Phi_arg13</title> +<ellipse fill="lightyellow" stroke="black" cx="341" cy="-393.67" rx="37.09" ry="18"/> +<text text-anchor="middle" x="341" y="-389.97" font-family="Times,serif" font-size="14.00">φ_arg</text> +</g> +<!-- Phi_arg13->Start2 --> +<g id="edge10" class="edge"> +<title>Phi_arg13->Start2:p1</title> +<path fill="none" stroke="black" d="M341.39,-411.71C341.18,-444.06 336.07,-514.83 298,-555.67 259.62,-596.84 181.02,-546.84 171.05,-589.48"/> +<polygon fill="black" stroke="black" points="167.54,-589.37 170,-599.67 174.5,-590.09 167.54,-589.37"/> +<text text-anchor="middle" x="338.39" y="-414.31" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_arg13->Loop11 --> +<g id="edge9" class="edge"> +<title>Phi_arg13->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M321.44,-378.23C310.28,-370.41 295.63,-361.73 281,-357.67 251.45,-349.49 240.94,-347.52 212,-357.67 204.86,-360.18 198.01,-364.35 191.91,-368.97"/> +<polygon fill="black" stroke="black" points="189.55,-366.38 184.08,-375.46 194.01,-371.77 189.55,-366.38"/> +<text text-anchor="middle" x="318.44" y="-371.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35 --> +<g id="node19" class="node"> +<title>Sub35</title> +<ellipse fill="none" stroke="black" cx="217" cy="-321.67" rx="27" ry="18"/> +<text text-anchor="middle" x="217" y="-317.97" font-family="Times,serif" font-size="14.00">-</text> +</g> +<!-- LT14 --> +<g id="node8" class="node"> +<title>LT14</title> +<ellipse fill="none" stroke="black" cx="289" cy="-321.67" rx="27" ry="18"/> +<text text-anchor="middle" x="289" y="-317.97" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT14->Con_4 --> +<g id="edge13" class="edge"> +<title>LT14->Con_4</title> +<path fill="none" stroke="black" d="M286.84,-339.83C282.17,-376.84 271.11,-464.49 265.46,-509.29"/> +<polygon fill="black" stroke="black" points="261.95,-509.08 264.17,-519.44 268.9,-509.96 261.95,-509.08"/> +<text text-anchor="middle" x="283.84" y="-342.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- LT14->Loop11 --> +<g id="edge12" class="edge"> +<title>LT14->Loop11</title> +<path fill="none" stroke="red" d="M269.13,-334.08C251.28,-344.31 224.66,-359.59 203.06,-371.98"/> +<polygon fill="red" stroke="red" points="201.09,-369.08 194.16,-377.09 204.57,-375.15 201.09,-369.08"/> +<text text-anchor="middle" x="266.13" y="-336.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- LT14->Phi_arg13 --> +<g id="edge14" class="edge"> +<title>LT14->Phi_arg13</title> +<path fill="none" stroke="black" d="M300.54,-338.21C307.02,-346.93 315.26,-358.02 322.61,-367.91"/> +<polygon fill="black" stroke="black" points="319.99,-370.27 328.77,-376.21 325.61,-366.09 319.99,-370.27"/> +<text text-anchor="middle" x="297.54" y="-340.81" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If16 --> +<g id="node9" class="node"> +<title>If16</title> +<polygon fill="yellow" stroke="transparent" points="205,-238.67 205,-263.67 299,-263.67 299,-238.67 205,-238.67"/> +<polygon fill="none" stroke="black" points="205,-238.67 205,-263.67 299,-263.67 299,-238.67 205,-238.67"/> +<text text-anchor="start" x="246.5" y="-247.47" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="205,-207.67 205,-238.67 299,-238.67 299,-207.67 205,-207.67"/> +<polygon fill="yellow" stroke="transparent" points="210,-212.67 210,-233.67 250,-233.67 250,-212.67 210,-212.67"/> +<polygon fill="none" stroke="black" points="210,-212.67 210,-233.67 250,-233.67 250,-212.67 210,-212.67"/> +<text text-anchor="start" x="213" y="-219.47" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="250,-212.67 250,-233.67 294,-233.67 294,-212.67 250,-212.67"/> +<polygon fill="none" stroke="black" points="250,-212.67 250,-233.67 294,-233.67 294,-212.67 250,-212.67"/> +<text text-anchor="start" x="253" y="-219.47" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If16->Loop11 --> +<g id="edge15" class="edge"> +<title>If16->Loop11</title> +<path fill="none" stroke="red" d="M209.76,-267.98C198.76,-278.17 188.03,-290.33 181,-303.67 170.94,-322.76 167.65,-347.11 166.77,-365.52"/> +<polygon fill="red" stroke="red" points="163.27,-365.49 166.49,-375.59 170.26,-365.69 163.27,-365.49"/> +<text text-anchor="middle" x="206.76" y="-270.58" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If16->LT14 --> +<g id="edge16" class="edge"> +<title>If16->LT14</title> +<path fill="none" stroke="black" d="M265.78,-267.95C269.68,-276.8 273.87,-286.34 277.62,-294.83"/> +<polygon fill="black" stroke="black" points="274.52,-296.48 281.75,-304.22 280.92,-293.65 274.52,-296.48"/> +<text text-anchor="middle" x="262.78" y="-270.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Div21 --> +<g id="node10" class="node"> +<title>Div21</title> +<ellipse fill="none" stroke="black" cx="43" cy="-537.67" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-533.97" font-family="Times,serif" font-size="14.00">//</text> +</g> +<!-- Div21->Start2 --> +<g id="edge17" class="edge"> +<title>Div21->Start2</title> +<path fill="none" stroke="red" d="M60.82,-551.25C73.8,-560.41 92.1,-573.33 109.58,-585.67"/> +<polygon fill="red" stroke="red" points="107.88,-588.75 118.07,-591.66 111.91,-583.04 107.88,-588.75"/> +<text text-anchor="middle" x="57.82" y="-553.85" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Div21->Start2 --> +<g id="edge18" class="edge"> +<title>Div21->Start2:p1</title> +<path fill="none" stroke="black" d="M64.54,-548.87C92.71,-562.22 140.81,-585.08 160.83,-594.93"/> +<polygon fill="black" stroke="black" points="159.51,-598.19 170,-599.67 162.72,-591.97 159.51,-598.19"/> +<text text-anchor="middle" x="67.54" y="-551.47" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_22 --> +<g id="node11" class="node"> +<title>Con_22</title> +<ellipse fill="none" stroke="black" cx="43" cy="-623.67" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-619.97" font-family="Times,serif" font-size="14.00">#3</text> +</g> +<!-- Div21->Con_22 --> +<g id="edge19" class="edge"> +<title>Div21->Con_22</title> +<path fill="none" stroke="black" d="M43,-556.08C43,-567.42 43,-582.45 43,-595.36"/> +<polygon fill="black" stroke="black" points="39.5,-595.58 43,-605.58 46.5,-595.58 39.5,-595.58"/> +<text text-anchor="middle" x="40" y="-558.68" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- EQ23 --> +<g id="node12" class="node"> +<title>EQ23</title> +<ellipse fill="none" stroke="black" cx="361" cy="-321.67" rx="27" ry="18"/> +<text text-anchor="middle" x="361" y="-317.97" font-family="Times,serif" font-size="14.00">==</text> +</g> +<!-- EQ23->Loop11 --> +<g id="edge20" class="edge"> +<title>EQ23->Loop11</title> +<path fill="none" stroke="red" d="M339.92,-333.3C335.08,-335.55 329.91,-337.8 325,-339.67 274.96,-358.74 257.65,-355.93 203.69,-375.71"/> +<polygon fill="red" stroke="red" points="202.46,-372.44 194.33,-379.24 204.92,-378.99 202.46,-372.44"/> +<text text-anchor="middle" x="336.92" y="-335.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- EQ23->Phi_arg13 --> +<g id="edge21" class="edge"> +<title>EQ23->Phi_arg13</title> +<path fill="none" stroke="black" d="M356.16,-339.62C353.93,-347.41 351.23,-356.85 348.74,-365.6"/> +<polygon fill="black" stroke="black" points="345.32,-364.82 345.94,-375.4 352.05,-366.75 345.32,-364.82"/> +<text text-anchor="middle" x="353.16" y="-342.22" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_24 --> +<g id="node13" class="node"> +<title>Con_24</title> +<ellipse fill="none" stroke="black" cx="423" cy="-393.67" rx="27" ry="18"/> +<text text-anchor="middle" x="423" y="-389.97" font-family="Times,serif" font-size="14.00">#5</text> +</g> +<!-- EQ23->Con_24 --> +<g id="edge22" class="edge"> +<title>EQ23->Con_24</title> +<path fill="none" stroke="black" d="M374.15,-337.52C382.53,-346.99 393.56,-359.44 403.02,-370.11"/> +<polygon fill="black" stroke="black" points="400.62,-372.69 409.87,-377.85 405.86,-368.04 400.62,-372.69"/> +<text text-anchor="middle" x="371.15" y="-340.12" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If25 --> +<g id="node14" class="node"> +<title>If25</title> +<polygon fill="yellow" stroke="transparent" points="210,-138.67 210,-163.67 304,-163.67 304,-138.67 210,-138.67"/> +<polygon fill="none" stroke="black" points="210,-138.67 210,-163.67 304,-163.67 304,-138.67 210,-138.67"/> +<text text-anchor="start" x="251.5" y="-147.47" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="210,-107.67 210,-138.67 304,-138.67 304,-107.67 210,-107.67"/> +<polygon fill="yellow" stroke="transparent" points="215,-112.67 215,-133.67 255,-133.67 255,-112.67 215,-112.67"/> +<polygon fill="none" stroke="black" points="215,-112.67 215,-133.67 255,-133.67 255,-112.67 215,-112.67"/> +<text text-anchor="start" x="218" y="-119.47" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="255,-112.67 255,-133.67 299,-133.67 299,-112.67 255,-112.67"/> +<polygon fill="none" stroke="black" points="255,-112.67 255,-133.67 299,-133.67 299,-112.67 255,-112.67"/> +<text text-anchor="start" x="258" y="-119.47" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If25->If16 --> +<g id="edge23" class="edge"> +<title>If25->If16:p0</title> +<path fill="none" stroke="red" d="M240.36,-167.91C236.06,-178.07 232.22,-189.75 230.69,-201.44"/> +<polygon fill="red" stroke="red" points="227.18,-201.46 230,-211.67 234.17,-201.93 227.18,-201.46"/> +<text text-anchor="middle" x="237.36" y="-170.51" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If25->EQ23 --> +<g id="edge24" class="edge"> +<title>If25->EQ23</title> +<path fill="none" stroke="black" d="M288.6,-167.9C298.28,-178.61 308.37,-191.08 316,-203.67 333.46,-232.5 346.27,-269.27 353.66,-293.82"/> +<polygon fill="black" stroke="black" points="350.33,-294.92 356.49,-303.54 357.05,-292.96 350.33,-294.92"/> +<text text-anchor="middle" x="285.6" y="-170.5" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230 --> +<g id="node15" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="89" cy="-393.67" rx="33.29" ry="18"/> +<text text-anchor="middle" x="89" y="-389.97" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_230->Store10 --> +<g id="edge26" class="edge"> +<title>Phi_230->Store10</title> +<path fill="none" stroke="blue" d="M100.36,-410.91C106.57,-419.78 114.38,-430.94 121.28,-440.78"/> +<polygon fill="blue" stroke="blue" points="118.43,-442.82 127.03,-449.01 124.17,-438.81 118.43,-442.82"/> +<text text-anchor="middle" x="97.36" y="-413.51" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230->Loop11 --> +<g id="edge25" class="edge"> +<title>Phi_230->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M122.21,-393.67C124.67,-393.67 127.13,-393.67 129.59,-393.67"/> +<polygon fill="black" stroke="black" points="129.71,-397.17 139.71,-393.67 129.71,-390.17 129.71,-397.17"/> +<text text-anchor="middle" x="125.21" y="-387.27" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_233 --> +<g id="node18" class="node"> +<title>Phi_233</title> +<ellipse fill="lightyellow" stroke="black" cx="61" cy="-49.67" rx="33.29" ry="18"/> +<text text-anchor="middle" x="61" y="-45.97" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Store31 --> +<g id="node16" class="node"> +<title>Store31</title> +<ellipse fill="none" stroke="black" cx="44" cy="-321.67" rx="27.9" ry="18"/> +<text text-anchor="middle" x="44" y="-317.97" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store31->new9 --> +<g id="edge30" class="edge"> +<title>Store31->new9</title> +<path fill="none" stroke="black" d="M30,-466.67C39.91,-492.39 65.61,-510.26 88.07,-521.5"/> +<polygon fill="black" stroke="black" points="86.58,-524.67 97.12,-525.77 89.57,-518.34 86.58,-524.67"/> +<text text-anchor="middle" x="27" y="-460.27" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store31->Loop11 --> +<g id="edge28" class="edge"> +<title>Store31->Loop11</title> +<path fill="none" stroke="red" d="M64.31,-334.23C82.34,-344.49 109.11,-359.73 130.8,-372.07"/> +<polygon fill="red" stroke="red" points="129.32,-375.26 139.75,-377.16 132.79,-369.18 129.32,-375.26"/> +<text text-anchor="middle" x="61.31" y="-336.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store31->Div21 --> +<g id="edge31" class="edge"> +<title>Store31->Div21</title> +<path fill="none" stroke="black" d="M37.26,-339.26C27.22,-366.66 11.4,-422.52 30,-464.67"/> +<path fill="none" stroke="black" d="M30,-466.67C35.26,-480.33 38.43,-496.32 40.33,-509.57"/> +<polygon fill="black" stroke="black" points="36.86,-510.03 41.59,-519.51 43.8,-509.15 36.86,-510.03"/> +<text text-anchor="middle" x="34.26" y="-341.86" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store31->Phi_230 --> +<g id="edge29" class="edge"> +<title>Store31->Phi_230</title> +<path fill="none" stroke="blue" d="M54.21,-338.56C59.77,-347.21 66.77,-358.1 73.04,-367.84"/> +<polygon fill="blue" stroke="blue" points="70.17,-369.85 78.52,-376.37 76.05,-366.06 70.17,-369.85"/> +<text text-anchor="middle" x="51.21" y="-341.16" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge32" class="edge"> +<title>Region32->If25:p0</title> +<path fill="none" stroke="red" d="M235,-67.77C235,-77.22 235,-89.45 235,-101.36"/> +<polygon fill="red" stroke="red" points="231.5,-101.67 235,-111.67 238.5,-101.67 231.5,-101.67"/> +<text text-anchor="middle" x="232" y="-70.37" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge33" class="edge"> +<title>Region32->If25:p1</title> +<path fill="none" stroke="red" d="M255.02,-67.8C263.64,-76.74 272.48,-88.51 275.71,-101.52"/> +<polygon fill="red" stroke="red" points="272.27,-102.19 277,-111.67 279.22,-101.31 272.27,-102.19"/> +<text text-anchor="middle" x="252.02" y="-70.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_233->Phi_230 --> +<g id="edge36" class="edge"> +<title>Phi_233->Phi_230</title> +<path fill="none" stroke="blue" d="M63.28,-78.47C68.55,-142.92 81.57,-301.9 86.79,-365.62"/> +<polygon fill="blue" stroke="blue" points="66.73,-77.79 62.43,-68.1 59.75,-78.36 66.73,-77.79"/> +<polygon fill="blue" stroke="blue" points="83.3,-366 87.61,-375.69 90.28,-365.43 83.3,-366"/> +<text text-anchor="middle" x="59.43" y="-70.7" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_233->Store31 --> +<g id="edge35" class="edge"> +<title>Phi_233->Store31</title> +<path fill="none" stroke="blue" d="M59.91,-67.97C57.03,-113.69 49.21,-238 45.72,-293.4"/> +<polygon fill="blue" stroke="blue" points="42.22,-293.32 45.08,-303.52 49.2,-293.76 42.22,-293.32"/> +<text text-anchor="middle" x="56.91" y="-70.57" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_233->Region32 --> +<g id="edge34" class="edge"> +<title>Phi_233->Region32</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M77.36,-33.99C89.26,-23.73 106.31,-11.24 124,-5.67 146.89,1.53 155.28,2.07 178,-5.67 189.71,-9.67 200.89,-17.16 210.2,-24.83"/> +<polygon fill="black" stroke="black" points="208.03,-27.59 217.86,-31.55 212.65,-22.33 208.03,-27.59"/> +<text text-anchor="middle" x="74.36" y="-27.59" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35->Loop11 --> +<g id="edge37" class="edge"> +<title>Sub35->Loop11</title> +<path fill="none" stroke="red" d="M205.9,-338.21C199.85,-346.68 192.2,-357.39 185.3,-367.06"/> +<polygon fill="red" stroke="red" points="182.44,-365.04 179.47,-375.21 188.13,-369.11 182.44,-365.04"/> +<text text-anchor="middle" x="202.9" y="-340.81" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35->Phi_arg13 --> +<g id="edge38" class="edge"> +<title>Sub35->Phi_arg13</title> +<path fill="none" stroke="black" d="M245.77,-338.92C264.66,-349.58 289.38,-363.54 308.91,-374.56"/> +<polygon fill="black" stroke="black" points="247.35,-335.79 236.92,-333.92 243.91,-341.89 247.35,-335.79"/> +<polygon fill="black" stroke="black" points="307.32,-377.68 317.75,-379.55 310.76,-371.58 307.32,-377.68"/> +<text text-anchor="middle" x="233.92" y="-336.52" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_36 --> +<g id="node20" class="node"> +<title>Con_36</title> +<ellipse fill="none" stroke="black" cx="239" cy="-393.67" rx="27" ry="18"/> +<text text-anchor="middle" x="239" y="-389.97" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Sub35->Con_36 --> +<g id="edge39" class="edge"> +<title>Sub35->Con_36</title> +<path fill="none" stroke="black" d="M222.33,-339.62C224.84,-347.62 227.9,-357.34 230.7,-366.27"/> +<polygon fill="black" stroke="black" points="227.39,-367.39 233.72,-375.89 234.06,-365.3 227.39,-367.39"/> +<text text-anchor="middle" x="219.33" y="-342.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->new9 --> +<g id="edge41" class="edge"> +<title>Return37->new9</title> +<path fill="none" stroke="black" d="M152.88,-153.84C154.6,-173.54 156.18,-206.74 151,-234.67"/> +<path fill="none" stroke="black" d="M151,-236.67C140.92,-291.1 115.67,-296.53 81,-339.67 67.22,-356.83 55.13,-355.22 47,-375.67 26.8,-426.48 71.69,-483.13 102.06,-513.85"/> +<polygon fill="black" stroke="black" points="99.83,-516.56 109.42,-521.08 104.74,-511.57 99.83,-516.56"/> +<text text-anchor="middle" x="149.88" y="-156.44" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return37->If16 --> +<g id="edge40" class="edge"> +<title>Return37->If16:p1</title> +<path fill="none" stroke="red" d="M173.3,-153.95C179.56,-158.57 186.45,-163.46 193,-167.67 223.49,-187.31 263.28,-174.61 270.76,-201.47"/> +<polygon fill="red" stroke="red" points="267.32,-202.17 272,-211.67 274.27,-201.33 267.32,-202.17"/> +<text text-anchor="middle" x="170.3" y="-156.55" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return37->Phi_230 --> +<g id="edge42" class="edge"> +<title>Return37->Phi_230</title> +<path fill="none" stroke="blue" d="M151,-236.67C145.42,-285.27 120.51,-337.2 104.01,-367.2"/> +<polygon fill="blue" stroke="blue" points="100.84,-365.71 98.99,-376.14 106.94,-369.14 100.84,-365.71"/> +<text text-anchor="middle" x="148" y="-230.27" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph6.gv b/chapter11/docs/graph6.gv new file mode 100644 index 00000000..b108c44d --- /dev/null +++ b/chapter11/docs/graph6.gv @@ -0,0 +1,113 @@ +digraph chapter11 { +/* +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + <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><TD PORT="p2">$2</TD></TR> + </TABLE> + </TD></TR> + </TABLE>> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_4 [ label="#0" ]; + new9 [ label="new S" ]; + Store10 [ label=".f =" ]; + Loop11 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + Phi_arg13 [ style=filled fillcolor=lightyellow label="φ_arg" ]; + LT14 [ label="<" ]; + If16 [ shape=plaintext label=< + <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>> + ]; + Div21 [ label="//" ]; + Con_22 [ label="#3" ]; + EQ23 [ label="==" ]; + Con_24 [ label="#5" ]; + If25 [ 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>> + ]; + Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Store31 [ label=".f =" ]; + Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Sub35 [ label="-" ]; + Con_36 [ label="#1" ]; + Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Loop11;Phi_230;Phi_arg13;} + { rank=same; Region32;Phi_233;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return37[taillabel=0 color=red]; + new9 -> Start2:p0[taillabel=0 color=green]; + Store10 -> Start2:p0[taillabel=0 color=red]; + Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> new9[taillabel=2]; + Store10 -> Con_4[taillabel=3]; + Loop11 -> Start2:p0[taillabel=1 color=red]; + Loop11 -> Region32[taillabel=2 color=red constraint=false]; + Phi_arg13 -> Loop11 [style=dotted taillabel=0]; + Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Sub35[taillabel=2 constraint=false]; + LT14 -> Loop11[taillabel=0 color=red]; + LT14 -> Con_4[taillabel=1]; + LT14 -> Phi_arg13[taillabel=2]; + If16 -> Loop11[taillabel=0 color=red]; + If16 -> LT14[taillabel=1]; + Div21 -> Start2:p0[taillabel=0 color=red]; + Div21 -> Start2:p1[taillabel=1]; + Div21 -> Con_22[taillabel=2]; + EQ23 -> If16:p0[taillabel=0 color=red]; + EQ23 -> Phi_arg13[taillabel=1]; + EQ23 -> Con_24[taillabel=2]; + If25 -> If16:p0[taillabel=0 color=red]; + If25 -> EQ23[taillabel=1]; + Phi_230 -> Loop11 [style=dotted taillabel=0]; + Phi_230 -> Store10[taillabel=1 color=blue]; + Phi_230 -> Phi_233[taillabel=2 color=blue constraint=false]; + Store31 -> If25:p0[taillabel=0 color=red]; + Store31 -> Phi_230[taillabel=1 color=blue]; + Store31 -> new9[taillabel=2]; + Store31 -> Div21[taillabel=3]; + Region32 -> If25:p0[taillabel=1 color=red]; + Region32 -> If25:p1[taillabel=2 color=red]; + Phi_233 -> Region32 [style=dotted taillabel=0]; + Phi_233 -> Store31[taillabel=1 color=blue]; + Phi_233 -> Phi_230[taillabel=2 color=blue constraint=false]; + Sub35 -> Region32[taillabel=0 color=red]; + Sub35 -> Phi_arg13[taillabel=1]; + Sub35 -> Con_36[taillabel=2]; + Return37 -> If16:p1[taillabel=0 color=red]; + Return37 -> new9[taillabel=1]; + Return37 -> Phi_230[taillabel=2 color=blue]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph6.svg b/chapter11/docs/graph6.svg new file mode 100644 index 00000000..354f5d2f --- /dev/null +++ b/chapter11/docs/graph6.svg @@ -0,0 +1,437 @@ +<?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: chapter11 Pages: 1 --> +<svg width="382pt" height="880pt" + viewBox="0.00 0.00 382.00 880.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 876)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-876 378,-876 378,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-864 366,-864 366,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="160,-827 160,-852 266,-852 266,-827 160,-827"/> +<polygon fill="none" stroke="black" points="160,-827 160,-852 266,-852 266,-827 160,-827"/> +<text text-anchor="start" x="194.5" y="-835.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="160,-796 160,-827 266,-827 266,-796 160,-796"/> +<polygon fill="yellow" stroke="transparent" points="165,-801 165,-822 205,-822 205,-801 165,-801"/> +<polygon fill="none" stroke="black" points="165,-801 165,-822 205,-822 205,-801 165,-801"/> +<text text-anchor="start" x="168" y="-807.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="205,-801 205,-822 236,-822 236,-801 205,-801"/> +<text text-anchor="start" x="208" y="-807.8" font-family="Times,serif" font-size="14.00">arg</text> +<polygon fill="none" stroke="black" points="236,-801 236,-822 261,-822 261,-801 236,-801"/> +<text text-anchor="start" x="239" y="-807.8" font-family="Times,serif" font-size="14.00">$2</text> +</g> +<!-- Stop3 --> +<g id="node2" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="150,-282 96,-282 96,-246 150,-246 150,-282"/> +<text text-anchor="middle" x="123" y="-260.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return37 --> +<g id="node21" class="node"> +<title>Return37</title> +<polygon fill="yellow" stroke="black" points="142,-368 76,-368 76,-332 142,-332 142,-368"/> +<text text-anchor="middle" x="109" y="-346.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return37 --> +<g id="edge1" class="edge"> +<title>Stop3->Return37</title> +<path fill="none" stroke="red" d="M120.1,-282.4C118.21,-293.74 115.7,-308.77 113.55,-321.68"/> +<polygon fill="red" stroke="red" points="110.04,-321.47 111.85,-331.9 116.95,-322.62 110.04,-321.47"/> +<text text-anchor="middle" x="117.1" y="-285" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_4 --> +<g id="node3" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="231" cy="-738" rx="27" ry="18"/> +<text text-anchor="middle" x="231" y="-734.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- new9 --> +<g id="node4" class="node"> +<title>new9</title> +<ellipse fill="none" stroke="black" cx="127" cy="-738" rx="38.99" ry="18"/> +<text text-anchor="middle" x="127" y="-734.3" font-family="Times,serif" font-size="14.00">new S</text> +</g> +<!-- new9->Start2 --> +<g id="edge2" class="edge"> +<title>new9->Start2:p0</title> +<path fill="none" stroke="green" d="M128.38,-756.06C130.74,-774.19 137.07,-801.28 154.09,-808.93"/> +<polygon fill="green" stroke="green" points="153.5,-812.38 164,-811 154.93,-805.53 153.5,-812.38"/> +<text text-anchor="middle" x="125.38" y="-758.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store10 --> +<g id="node5" class="node"> +<title>Store10</title> +<ellipse fill="none" stroke="black" cx="157" cy="-666" rx="27.9" ry="18"/> +<text text-anchor="middle" x="157" y="-662.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store10->Start2 --> +<g id="edge3" class="edge"> +<title>Store10->Start2:p0</title> +<path fill="none" stroke="red" d="M163.73,-683.93C167.57,-694.21 172.17,-707.69 175,-720 182.21,-751.38 184.54,-762.02 184.93,-789.85"/> +<polygon fill="red" stroke="red" points="181.44,-790.02 185,-800 188.44,-789.98 181.44,-790.02"/> +<text text-anchor="middle" x="160.73" y="-686.53" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store10->Con_4 --> +<g id="edge6" class="edge"> +<title>Store10->Con_4</title> +<path fill="none" stroke="black" d="M172.33,-681.5C182.82,-691.42 196.89,-704.74 208.63,-715.84"/> +<polygon fill="black" stroke="black" points="206.27,-718.42 215.94,-722.75 211.08,-713.34 206.27,-718.42"/> +<text text-anchor="middle" x="169.33" y="-684.1" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store10->new9 --> +<g id="edge5" class="edge"> +<title>Store10->new9</title> +<path fill="none" stroke="black" d="M149.89,-683.59C146.43,-691.66 142.18,-701.57 138.29,-710.65"/> +<polygon fill="black" stroke="black" points="134.99,-709.47 134.27,-720.04 141.42,-712.23 134.99,-709.47"/> +<text text-anchor="middle" x="146.89" y="-686.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Loop11 --> +<g id="node6" class="node"> +<title>Loop11</title> +<polygon fill="yellow" stroke="black" points="246,-612 192,-612 192,-576 246,-576 246,-612"/> +<text text-anchor="middle" x="219" y="-590.3" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop11->Start2 --> +<g id="edge7" class="edge"> +<title>Loop11->Start2:p0</title> +<path fill="none" stroke="red" d="M232.6,-612.1C255.23,-642.79 295.45,-708.44 267,-756 247.84,-788.03 196.49,-764.41 186.65,-789.87"/> +<polygon fill="red" stroke="red" points="183.15,-789.57 185,-800 190.06,-790.69 183.15,-789.57"/> +<text text-anchor="middle" x="229.6" y="-614.7" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32 --> +<g id="node17" class="node"> +<title>Region32</title> +<polygon fill="yellow" stroke="black" points="266,-124 200,-124 200,-88 266,-88 266,-124"/> +<text text-anchor="middle" x="233" y="-102.3" font-family="Times,serif" font-size="14.00">Region</text> +</g> +<!-- Loop11->Region32 --> +<g id="edge8" class="edge"> +<title>Loop11->Region32</title> +<path fill="none" stroke="red" d="M191.95,-583.41C149.79,-568.45 73.1,-541.12 72,-540 38.13,-505.5 37,-485.35 37,-437 37,-349 37,-349 37,-349 37,-292.45 45.93,-270.87 87,-232 121.01,-199.82 150.7,-226.76 186,-196 204.96,-179.48 217.45,-153.41 224.76,-133.74"/> +<polygon fill="red" stroke="red" points="228.13,-134.72 228.12,-124.13 221.52,-132.42 228.13,-134.72"/> +<text text-anchor="middle" x="188.95" y="-577.01" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_arg13 --> +<g id="node7" class="node"> +<title>Phi_arg13</title> +<ellipse fill="lightyellow" stroke="black" cx="321" cy="-594" rx="37.09" ry="18"/> +<text text-anchor="middle" x="321" y="-590.3" font-family="Times,serif" font-size="14.00">φ_arg</text> +</g> +<!-- Phi_arg13->Start2 --> +<g id="edge10" class="edge"> +<title>Phi_arg13->Start2:p1</title> +<path fill="none" stroke="black" d="M319.63,-612.41C316.47,-643.6 306.42,-710.01 276,-756 261.03,-778.64 230.66,-771.56 222.86,-789.7"/> +<polygon fill="black" stroke="black" points="219.33,-789.54 221,-800 226.22,-790.78 219.33,-789.54"/> +<text text-anchor="middle" x="316.63" y="-615.01" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_arg13->Loop11 --> +<g id="edge9" class="edge"> +<title>Phi_arg13->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M283.84,-594C274.6,-594 265.36,-594 256.12,-594"/> +<polygon fill="black" stroke="black" points="256.09,-590.5 246.09,-594 256.09,-597.5 256.09,-590.5"/> +<text text-anchor="middle" x="280.84" y="-587.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35 --> +<g id="node19" class="node"> +<title>Sub35</title> +<ellipse fill="none" stroke="black" cx="285" cy="-34" rx="27" ry="18"/> +<text text-anchor="middle" x="285" y="-30.3" font-family="Times,serif" font-size="14.00">-</text> +</g> +<!-- LT14 --> +<g id="node8" class="node"> +<title>LT14</title> +<ellipse fill="none" stroke="black" cx="265" cy="-522" rx="27" ry="18"/> +<text text-anchor="middle" x="265" y="-518.3" font-family="Times,serif" font-size="14.00"><</text> +</g> +<!-- LT14->Con_4 --> +<g id="edge13" class="edge"> +<title>LT14->Con_4</title> +<path fill="none" stroke="black" d="M263.48,-540.11C261.78,-558.07 258.78,-587.1 255,-612 249.83,-646.03 242.07,-684.92 236.78,-710.16"/> +<polygon fill="black" stroke="black" points="233.33,-709.58 234.68,-720.09 240.17,-711.03 233.33,-709.58"/> +<text text-anchor="middle" x="260.48" y="-542.71" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- LT14->Loop11 --> +<g id="edge12" class="edge"> +<title>LT14->Loop11</title> +<path fill="none" stroke="red" d="M254.56,-538.88C249.04,-547.28 242.13,-557.8 235.87,-567.33"/> +<polygon fill="red" stroke="red" points="232.93,-565.43 230.36,-575.71 238.78,-569.27 232.93,-565.43"/> +<text text-anchor="middle" x="251.56" y="-541.48" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- LT14->Phi_arg13 --> +<g id="edge14" class="edge"> +<title>LT14->Phi_arg13</title> +<path fill="none" stroke="black" d="M277.15,-538.19C284.36,-547.2 293.66,-558.82 301.84,-569.05"/> +<polygon fill="black" stroke="black" points="299.11,-571.24 308.09,-576.87 304.58,-566.87 299.11,-571.24"/> +<text text-anchor="middle" x="274.15" y="-540.79" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If16 --> +<g id="node9" class="node"> +<title>If16</title> +<polygon fill="yellow" stroke="transparent" points="144,-439 144,-464 238,-464 238,-439 144,-439"/> +<polygon fill="none" stroke="black" points="144,-439 144,-464 238,-464 238,-439 144,-439"/> +<text text-anchor="start" x="185.5" y="-447.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="144,-408 144,-439 238,-439 238,-408 144,-408"/> +<polygon fill="yellow" stroke="transparent" points="149,-413 149,-434 189,-434 189,-413 149,-413"/> +<polygon fill="none" stroke="black" points="149,-413 149,-434 189,-434 189,-413 149,-413"/> +<text text-anchor="start" x="152" y="-419.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="189,-413 189,-434 233,-434 233,-413 189,-413"/> +<polygon fill="none" stroke="black" points="189,-413 189,-434 233,-434 233,-413 189,-413"/> +<text text-anchor="start" x="192" y="-419.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If16->Loop11 --> +<g id="edge15" class="edge"> +<title>If16->Loop11</title> +<path fill="none" stroke="red" d="M196.6,-468.19C201.73,-496.77 209.24,-538.64 214.13,-565.86"/> +<polygon fill="red" stroke="red" points="210.71,-566.63 215.92,-575.86 217.6,-565.4 210.71,-566.63"/> +<text text-anchor="middle" x="193.6" y="-470.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If16->LT14 --> +<g id="edge16" class="edge"> +<title>If16->LT14</title> +<path fill="none" stroke="black" d="M218.55,-468.28C227.33,-478.24 236.86,-489.05 244.99,-498.29"/> +<polygon fill="black" stroke="black" points="242.49,-500.74 251.72,-505.93 247.74,-496.11 242.49,-500.74"/> +<text text-anchor="middle" x="215.55" y="-470.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Div21 --> +<g id="node10" class="node"> +<title>Div21</title> +<ellipse fill="none" stroke="black" cx="43" cy="-738" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-734.3" font-family="Times,serif" font-size="14.00">//</text> +</g> +<!-- Div21->Start2 --> +<g id="edge17" class="edge"> +<title>Div21->Start2:p0</title> +<path fill="none" stroke="red" d="M56.68,-753.92C76,-773.4 113.45,-805.6 153.93,-810.4"/> +<polygon fill="red" stroke="red" points="153.81,-813.89 164,-811 154.23,-806.91 153.81,-813.89"/> +<text text-anchor="middle" x="53.68" y="-756.52" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Div21->Start2 --> +<g id="edge18" class="edge"> +<title>Div21->Start2:p1</title> +<path fill="none" stroke="black" d="M64.1,-749.57C68.94,-751.82 74.1,-754.1 79,-756 137.09,-778.59 211.95,-738.92 220.24,-789.82"/> +<polygon fill="black" stroke="black" points="216.77,-790.29 221,-800 223.75,-789.77 216.77,-790.29"/> +<text text-anchor="middle" x="61.1" y="-752.17" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Con_22 --> +<g id="node11" class="node"> +<title>Con_22</title> +<ellipse fill="none" stroke="black" cx="43" cy="-824" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-820.3" font-family="Times,serif" font-size="14.00">#3</text> +</g> +<!-- Div21->Con_22 --> +<g id="edge19" class="edge"> +<title>Div21->Con_22</title> +<path fill="none" stroke="black" d="M43,-756.4C43,-767.74 43,-782.77 43,-795.68"/> +<polygon fill="black" stroke="black" points="39.5,-795.9 43,-805.9 46.5,-795.9 39.5,-795.9"/> +<text text-anchor="middle" x="40" y="-759" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- EQ23 --> +<g id="node12" class="node"> +<title>EQ23</title> +<ellipse fill="none" stroke="black" cx="284" cy="-350" rx="27" ry="18"/> +<text text-anchor="middle" x="284" y="-346.3" font-family="Times,serif" font-size="14.00">==</text> +</g> +<!-- EQ23->Phi_arg13 --> +<g id="edge21" class="edge"> +<title>EQ23->Phi_arg13</title> +<path fill="none" stroke="black" d="M299.94,-365.01C309.81,-374.8 321.61,-388.81 327,-404 346.42,-458.77 336.12,-528.17 327.91,-565.78"/> +<polygon fill="black" stroke="black" points="324.4,-565.41 325.57,-575.94 331.22,-566.98 324.4,-565.41"/> +<text text-anchor="middle" x="296.94" y="-367.61" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- EQ23->If16 --> +<g id="edge20" class="edge"> +<title>EQ23->If16:p0</title> +<path fill="none" stroke="red" d="M259.89,-358.47C231.44,-367.9 186.29,-385 172.88,-402.37"/> +<polygon fill="red" stroke="red" points="169.49,-401.42 169,-412 175.98,-404.03 169.49,-401.42"/> +<text text-anchor="middle" x="256.89" y="-352.07" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_24 --> +<g id="node13" class="node"> +<title>Con_24</title> +<ellipse fill="none" stroke="black" cx="291" cy="-436" rx="27" ry="18"/> +<text text-anchor="middle" x="291" y="-432.3" font-family="Times,serif" font-size="14.00">#5</text> +</g> +<!-- EQ23->Con_24 --> +<g id="edge22" class="edge"> +<title>EQ23->Con_24</title> +<path fill="none" stroke="black" d="M285.42,-368C286.36,-379.31 287.62,-394.42 288.7,-407.43"/> +<polygon fill="black" stroke="black" points="285.24,-408.06 289.56,-417.74 292.22,-407.48 285.24,-408.06"/> +<text text-anchor="middle" x="282.42" y="-370.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- If25 --> +<g id="node14" class="node"> +<title>If25</title> +<polygon fill="yellow" stroke="transparent" points="208,-267 208,-292 302,-292 302,-267 208,-267"/> +<polygon fill="none" stroke="black" points="208,-267 208,-292 302,-292 302,-267 208,-267"/> +<text text-anchor="start" x="249.5" y="-275.8" font-family="Times,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="208,-236 208,-267 302,-267 302,-236 208,-236"/> +<polygon fill="yellow" stroke="transparent" points="213,-241 213,-262 253,-262 253,-241 213,-241"/> +<polygon fill="none" stroke="black" points="213,-241 213,-262 253,-262 253,-241 213,-241"/> +<text text-anchor="start" x="216" y="-247.8" font-family="Times,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="transparent" points="253,-241 253,-262 297,-262 297,-241 253,-241"/> +<polygon fill="none" stroke="black" points="253,-241 253,-262 297,-262 297,-241 253,-241"/> +<text text-anchor="start" x="256" y="-247.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- If25->If16 --> +<g id="edge23" class="edge"> +<title>If25->If16:p0</title> +<path fill="none" stroke="red" d="M234.4,-296.29C213.03,-329.21 181.43,-379.35 171.85,-402.21"/> +<polygon fill="red" stroke="red" points="168.44,-401.42 169,-412 175.16,-403.38 168.44,-401.42"/> +<text text-anchor="middle" x="231.4" y="-298.89" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If25->EQ23 --> +<g id="edge24" class="edge"> +<title>If25->EQ23</title> +<path fill="none" stroke="black" d="M265.8,-296.28C268.78,-304.9 271.97,-314.17 274.85,-322.49"/> +<polygon fill="black" stroke="black" points="271.58,-323.76 278.16,-332.07 278.2,-321.48 271.58,-323.76"/> +<text text-anchor="middle" x="262.8" y="-298.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230 --> +<g id="node15" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="129" cy="-594" rx="33.29" ry="18"/> +<text text-anchor="middle" x="129" y="-590.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Phi_230->Store10 --> +<g id="edge26" class="edge"> +<title>Phi_230->Store10</title> +<path fill="none" stroke="blue" d="M135.78,-611.95C139.01,-620.03 142.95,-629.88 146.56,-638.89"/> +<polygon fill="blue" stroke="blue" points="143.32,-640.23 150.28,-648.21 149.82,-637.63 143.32,-640.23"/> +<text text-anchor="middle" x="132.78" y="-614.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230->Loop11 --> +<g id="edge25" class="edge"> +<title>Phi_230->Loop11</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M162.4,-594C168.78,-594 175.17,-594 181.55,-594"/> +<polygon fill="black" stroke="black" points="181.58,-597.5 191.58,-594 181.58,-590.5 181.58,-597.5"/> +<text text-anchor="middle" x="165.4" y="-587.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_233 --> +<g id="node18" class="node"> +<title>Phi_233</title> +<ellipse fill="lightyellow" stroke="black" cx="149" cy="-106" rx="33.29" ry="18"/> +<text text-anchor="middle" x="149" y="-102.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +</g> +<!-- Store31 --> +<g id="node16" class="node"> +<title>Store31</title> +<ellipse fill="none" stroke="black" cx="149" cy="-178" rx="27.9" ry="18"/> +<text text-anchor="middle" x="149" y="-174.3" font-family="Times,serif" font-size="14.00">.f =</text> +</g> +<!-- Store31->new9 --> +<g id="edge30" class="edge"> +<title>Store31->new9</title> +<path fill="none" stroke="black" d="M125.79,-188.29C107.7,-196.82 83.59,-211.3 71,-232 43.78,-276.74 57,-296.63 57,-349 57,-349 57,-349 57,-437 57,-506.9 72.18,-523.27 77,-593"/> +<path fill="none" stroke="black" d="M77,-595C78.62,-637.52 98.4,-683.39 112.59,-711.15"/> +<polygon fill="black" stroke="black" points="109.59,-712.96 117.34,-720.18 115.78,-709.7 109.59,-712.96"/> +<text text-anchor="middle" x="122.79" y="-190.89" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Store31->Div21 --> +<g id="edge31" class="edge"> +<title>Store31->Div21</title> +<path fill="none" stroke="black" d="M122.62,-184.31C96.9,-190.92 58.72,-204.83 38,-232 5.97,-274.01 17,-296.17 17,-349 17,-349 17,-349 17,-595 17,-635.67 27.45,-681.83 35.1,-710.19"/> +<polygon fill="black" stroke="black" points="31.81,-711.44 37.86,-720.13 38.55,-709.56 31.81,-711.44"/> +<text text-anchor="middle" x="119.62" y="-177.91" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +</g> +<!-- Store31->If25 --> +<g id="edge28" class="edge"> +<title>Store31->If25:p0</title> +<path fill="none" stroke="red" d="M160.65,-194.62C172.83,-210.67 191.77,-234.77 203.47,-245.51"/> +<polygon fill="red" stroke="red" points="201.7,-248.53 212,-251 205.49,-242.64 201.7,-248.53"/> +<text text-anchor="middle" x="157.65" y="-197.22" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Store31->Phi_230 --> +<g id="edge29" class="edge"> +<title>Store31->Phi_230</title> +<path fill="none" stroke="blue" d="M153.06,-196.07C155.31,-206.4 157.87,-219.88 159,-232 161.65,-260.32 160.57,-267.6 159,-296 157.22,-328.15 162.02,-337.75 151,-368 144.42,-386.07 132.57,-385.6 127,-404 118.76,-431.22 126.77,-439.56 127,-468 127.27,-501.46 127.94,-539.96 128.43,-565.29"/> +<polygon fill="blue" stroke="blue" points="124.94,-565.68 128.63,-575.61 131.94,-565.54 124.94,-565.68"/> +<text text-anchor="middle" x="150.06" y="-198.67" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge32" class="edge"> +<title>Region32->If25:p0</title> +<path fill="none" stroke="red" d="M233,-124.22C233,-147.49 233,-190.58 233,-229.64"/> +<polygon fill="red" stroke="red" points="229.5,-230 233,-240 236.5,-230 229.5,-230"/> +<text text-anchor="middle" x="230" y="-126.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_233->Phi_230 --> +<g id="edge36" class="edge"> +<title>Phi_233->Phi_230</title> +<path fill="none" stroke="blue" d="M170.5,-129.96C185.43,-148.79 200.3,-175.58 186,-196 159.14,-234.35 113.86,-193.65 87,-232 70.68,-255.3 72.64,-271.45 87,-296 103.48,-324.17 134.52,-303.83 151,-332 159.08,-345.81 156.69,-353.04 151,-368 143.39,-388.01 126.61,-383.99 119,-404 98.19,-458.74 111.2,-528.88 121.03,-566.43"/> +<polygon fill="blue" stroke="blue" points="173.07,-127.58 163.96,-122.17 167.71,-132.08 173.07,-127.58"/> +<polygon fill="blue" stroke="blue" points="117.67,-567.4 123.69,-576.12 124.42,-565.55 117.67,-567.4"/> +<text text-anchor="middle" x="160.96" y="-124.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Phi_233->Store31 --> +<g id="edge35" class="edge"> +<title>Phi_233->Store31</title> +<path fill="none" stroke="blue" d="M149,-124.3C149,-132.02 149,-141.29 149,-149.89"/> +<polygon fill="blue" stroke="blue" points="145.5,-149.9 149,-159.9 152.5,-149.9 145.5,-149.9"/> +<text text-anchor="middle" x="146" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_233->Region32 --> +<g id="edge34" class="edge"> +<title>Phi_233->Region32</title> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M182.47,-106C184.91,-106 187.35,-106 189.8,-106"/> +<polygon fill="black" stroke="black" points="189.84,-109.5 199.84,-106 189.84,-102.5 189.84,-109.5"/> +<text text-anchor="middle" x="185.47" y="-99.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Sub35->Phi_arg13 --> +<g id="edge38" class="edge"> +<title>Sub35->Phi_arg13</title> +<path fill="none" stroke="black" d="M287.31,-62.31C288.99,-79.97 291.57,-103.42 295,-124 303.09,-172.5 311.09,-183.47 319,-232 331.36,-307.86 331.84,-327.31 337,-404 339.98,-448.36 339.33,-459.54 340,-504 340.24,-520 342.74,-524.24 340,-540 338.44,-549 335.48,-558.49 332.36,-566.91"/> +<polygon fill="black" stroke="black" points="290.77,-61.78 286.38,-52.14 283.8,-62.41 290.77,-61.78"/> +<polygon fill="black" stroke="black" points="329.1,-565.63 328.7,-576.22 335.62,-568.2 329.1,-565.63"/> +<text text-anchor="middle" x="283.38" y="-54.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Sub35->Region32 --> +<g id="edge37" class="edge"> +<title>Sub35->Region32</title> +<path fill="none" stroke="red" d="M273.46,-50.54C267.16,-59.01 259.21,-69.71 252.03,-79.39"/> +<polygon fill="red" stroke="red" points="249.12,-77.42 245.97,-87.54 254.74,-81.6 249.12,-77.42"/> +<text text-anchor="middle" x="270.46" y="-53.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Con_36 --> +<g id="node20" class="node"> +<title>Con_36</title> +<ellipse fill="none" stroke="black" cx="331" cy="-106" rx="27" ry="18"/> +<text text-anchor="middle" x="331" y="-102.3" font-family="Times,serif" font-size="14.00">#1</text> +</g> +<!-- Sub35->Con_36 --> +<g id="edge39" class="edge"> +<title>Sub35->Con_36</title> +<path fill="none" stroke="black" d="M295.44,-50.88C301.2,-59.66 308.48,-70.74 314.96,-80.58"/> +<polygon fill="black" stroke="black" points="312.19,-82.74 320.6,-89.18 318.04,-78.9 312.19,-82.74"/> +<text text-anchor="middle" x="292.44" y="-53.48" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->new9 --> +<g id="edge41" class="edge"> +<title>Return37->new9</title> +<path fill="none" stroke="black" d="M101.56,-368.35C89.32,-399.07 67.21,-465.09 77,-521"/> +<path fill="none" stroke="black" d="M77,-523C81.47,-553.79 74.85,-561.96 77,-593"/> +<text text-anchor="middle" x="98.56" y="-370.95" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Return37->If16 --> +<g id="edge40" class="edge"> +<title>Return37->If16:p1</title> +<path fill="none" stroke="red" d="M142.12,-356.57C168.64,-362.93 202.56,-375.99 209.66,-401.87"/> +<polygon fill="red" stroke="red" points="206.22,-402.55 211,-412 213.16,-401.63 206.22,-402.55"/> +<text text-anchor="middle" x="145.12" y="-350.17" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return37->Phi_230 --> +<g id="edge42" class="edge"> +<title>Return37->Phi_230</title> +<path fill="none" stroke="blue" d="M77,-523C80.24,-541.52 92.58,-558.55 104.43,-571.18"/> +<polygon fill="blue" stroke="blue" points="102.12,-573.82 111.65,-578.44 107.08,-568.88 102.12,-573.82"/> +<text text-anchor="middle" x="74" y="-516.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph7.gv b/chapter11/docs/graph7.gv new file mode 100644 index 00000000..e9e380ff --- /dev/null +++ b/chapter11/docs/graph7.gv @@ -0,0 +1,23 @@ +digraph chapter11 { + rankdir=BT; + 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>> + ]; + Con_4 [ label="#0" ]; + Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + { rank=same; Loop8;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Loop8 -> Start2:p0[taillabel=1 color=red]; + Loop8 -> Loop8[taillabel=2 color=red constraint=false]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph7.svg b/chapter11/docs/graph7.svg new file mode 100644 index 00000000..854660ec --- /dev/null +++ b/chapter11/docs/graph7.svg @@ -0,0 +1,54 @@ +<?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: chapter11 Pages: 1 --> +<svg width="184pt" height="176pt" + viewBox="0.00 0.00 184.00 176.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 172)"> +<title>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-172 180,-172 180,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-160 168,-160 168,-8 8,-8"/> +</g> +<!-- Start2 --> +<g id="node1" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="24,-123 24,-148 74,-148 74,-123 24,-123"/> +<polygon fill="none" stroke="black" points="24,-123 24,-148 74,-148 74,-123 24,-123"/> +<text text-anchor="start" x="30.5" y="-131.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="24,-92 24,-123 74,-123 74,-92 24,-92"/> +<polygon fill="yellow" stroke="transparent" points="29,-97 29,-118 69,-118 69,-97 29,-97"/> +<polygon fill="none" stroke="black" points="29,-97 29,-118 69,-118 69,-97 29,-97"/> +<text text-anchor="start" x="32" y="-103.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +</g> +<!-- Con_4 --> +<g id="node2" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="133" cy="-34" rx="27" ry="18"/> +<text text-anchor="middle" x="133" y="-30.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- Loop8 --> +<g id="node3" class="node"> +<title>Loop8</title> +<polygon fill="yellow" stroke="black" points="70,-52 16,-52 16,-16 70,-16 70,-52"/> +<text text-anchor="middle" x="43" y="-30.3" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Loop8->Start2 --> +<g id="edge1" class="edge"> +<title>Loop8->Start2:p0</title> +<path fill="none" stroke="red" d="M45.58,-52.05C46.84,-61.49 48.2,-73.71 48.75,-85.65"/> +<polygon fill="red" stroke="red" points="45.26,-86.09 49,-96 52.25,-85.92 45.26,-86.09"/> +<text text-anchor="middle" x="42.58" y="-54.65" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Loop8->Loop8 --> +<g id="edge2" class="edge"> +<title>Loop8->Loop8</title> +<path fill="none" stroke="red" d="M70.24,-17.89C80.02,-17.54 88,-22.91 88,-34 88,-40.93 84.88,-45.62 80.16,-48.09"/> +<polygon fill="red" stroke="red" points="79.34,-44.68 70.24,-50.11 80.74,-51.54 79.34,-44.68"/> +<text text-anchor="middle" x="73.24" y="-11.49" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +</g> +</svg> diff --git a/chapter11/docs/graph8.gv b/chapter11/docs/graph8.gv new file mode 100644 index 00000000..4189dae8 --- /dev/null +++ b/chapter11/docs/graph8.gv @@ -0,0 +1,45 @@ +digraph chapter11 { +/* +while (1) {} +return 0; + +*/ + rankdir=BT; + concentrate="true"; + compound="true"; + subgraph cluster_Nodes { + Never18 [ shape=plaintext label=< + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> + <TR><TD BGCOLOR="yellow">Never</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>> + ]; + 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_4 [ label="#0" ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ]; + { rank=same; Loop8;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Never18 -> Loop8[taillabel=0 color=red]; + Never18 -> Con_4[taillabel=1]; + Stop3 -> Return21[taillabel=0 color=red]; + Return21 -> Never18:p0[taillabel=0 color=red]; + Return21 -> Con_4[taillabel=1]; + Loop8 -> Start2:p0[taillabel=1 color=red]; + Loop8 -> Never18:p1[taillabel=2 color=red constraint=false]; +}
\ No newline at end of file diff --git a/chapter11/docs/graph8.svg b/chapter11/docs/graph8.svg new file mode 100644 index 00000000..c577cf84 --- /dev/null +++ b/chapter11/docs/graph8.svg @@ -0,0 +1,115 @@ +<?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: chapter11 Pages: 1 --> +<svg width="170pt" height="420pt" + viewBox="0.00 0.00 170.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>chapter11</title> +<polygon fill="white" stroke="transparent" points="-4,4 -4,-416 166,-416 166,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_Nodes</title> +<polygon fill="none" stroke="black" points="8,-8 8,-404 154,-404 154,-8 8,-8"/> +</g> +<!-- Never18 --> +<g id="node1" class="node"> +<title>Never18</title> +<polygon fill="yellow" stroke="transparent" points="24,-195 24,-220 118,-220 118,-195 24,-195"/> +<polygon fill="none" stroke="black" points="24,-195 24,-220 118,-220 118,-195 24,-195"/> +<text text-anchor="start" x="49" y="-203.8" font-family="Times,serif" font-size="14.00">Never</text> +<polygon fill="none" stroke="black" points="24,-164 24,-195 118,-195 118,-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">True</text> +<polygon fill="yellow" stroke="transparent" points="69,-169 69,-190 113,-190 113,-169 69,-169"/> +<polygon fill="none" stroke="black" points="69,-169 69,-190 113,-190 113,-169 69,-169"/> +<text text-anchor="start" x="72" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +</g> +<!-- Con_4 --> +<g id="node4" class="node"> +<title>Con_4</title> +<ellipse fill="none" stroke="black" cx="119" cy="-278" rx="27" ry="18"/> +<text text-anchor="middle" x="119" y="-274.3" font-family="Times,serif" font-size="14.00">#0</text> +</g> +<!-- Never18->Con_4 --> +<g id="edge2" class="edge"> +<title>Never18->Con_4</title> +<path fill="none" stroke="black" d="M88.87,-224.28C94.12,-233.46 99.78,-243.37 104.77,-252.1"/> +<polygon fill="black" stroke="black" points="101.86,-254.06 109.86,-261.01 107.94,-250.59 101.86,-254.06"/> +<text text-anchor="middle" x="85.87" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Loop8 --> +<g id="node6" class="node"> +<title>Loop8</title> +<polygon fill="yellow" stroke="black" points="74,-296 20,-296 20,-260 74,-260 74,-296"/> +<text text-anchor="middle" x="47" y="-274.3" font-family="Times,serif" font-size="14.00">Loop</text> +</g> +<!-- Never18->Loop8 --> +<g id="edge1" class="edge"> +<title>Never18->Loop8</title> +<path fill="none" stroke="red" d="M62.06,-224.28C59.66,-232.67 57.09,-241.68 54.76,-249.84"/> +<polygon fill="red" stroke="red" points="51.35,-249.02 51.97,-259.6 58.09,-250.94 51.35,-249.02"/> +<text text-anchor="middle" x="59.06" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Start2 --> +<g id="node2" class="node"> +<title>Start2</title> +<polygon fill="yellow" stroke="transparent" points="24,-367 24,-392 74,-392 74,-367 24,-367"/> +<polygon fill="none" stroke="black" points="24,-367 24,-392 74,-392 74,-367 24,-367"/> +<text text-anchor="start" x="30.5" y="-375.8" font-family="Times,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="24,-336 24,-367 74,-367 74,-336 24,-336"/> +<polygon fill="yellow" stroke="transparent" points="29,-341 29,-362 69,-362 69,-341 29,-341"/> +<polygon fill="none" stroke="black" points="29,-341 29,-362 69,-362 69,-341 29,-341"/> +<text text-anchor="start" x="32" y="-347.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +</g> +<!-- Stop3 --> +<g id="node3" class="node"> +<title>Stop3</title> +<polygon fill="yellow" stroke="black" points="108,-52 54,-52 54,-16 108,-16 108,-52"/> +<text text-anchor="middle" x="81" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +</g> +<!-- Return21 --> +<g id="node5" class="node"> +<title>Return21</title> +<polygon fill="yellow" stroke="black" points="114,-124 48,-124 48,-88 114,-88 114,-124"/> +<text text-anchor="middle" x="81" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +</g> +<!-- Stop3->Return21 --> +<g id="edge3" class="edge"> +<title>Stop3->Return21</title> +<path fill="none" stroke="red" d="M81,-52.3C81,-60.02 81,-69.29 81,-77.89"/> +<polygon fill="red" stroke="red" points="77.5,-77.9 81,-87.9 84.5,-77.9 77.5,-77.9"/> +<text text-anchor="middle" x="78" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return21->Never18 --> +<g id="edge4" class="edge"> +<title>Return21->Never18:p0</title> +<path fill="none" stroke="red" d="M66.31,-124.18C59.67,-133.33 52.73,-145.27 50.1,-157.83"/> +<polygon fill="red" stroke="red" points="46.6,-157.68 49,-168 53.56,-158.43 46.6,-157.68"/> +<text text-anchor="middle" x="63.31" y="-126.78" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return21->Con_4 --> +<g id="edge5" class="edge"> +<title>Return21->Con_4</title> +<path fill="none" stroke="black" d="M105.93,-124.14C117.06,-133.33 129,-145.71 135,-160 147.36,-189.44 138.76,-226.37 130.12,-250.77"/> +<polygon fill="black" stroke="black" points="126.74,-249.81 126.48,-260.41 133.29,-252.29 126.74,-249.81"/> +<text text-anchor="middle" x="102.93" y="-126.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Loop8->Never18 --> +<g id="edge7" class="edge"> +<title>Loop8->Never18:p1</title> +<path fill="none" stroke="red" d="M74.25,-262.42C94.74,-250.81 120.25,-234.78 126,-224 137.5,-202.43 143.04,-186.53 126,-169 118.29,-161.07 103.85,-156.04 96.15,-159.39"/> +<polygon fill="red" stroke="red" points="93.13,-157.62 91,-168 99.14,-161.22 93.13,-157.62"/> +<text text-anchor="middle" x="77.25" y="-256.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Loop8->Start2 --> +<g id="edge6" class="edge"> +<title>Loop8->Start2:p0</title> +<path fill="none" stroke="red" d="M47.86,-296.09C48.28,-305.54 48.73,-317.77 48.92,-329.68"/> +<polygon fill="red" stroke="red" points="45.42,-330.03 49,-340 52.42,-329.97 45.42,-330.03"/> +<text text-anchor="middle" x="44.86" y="-298.69" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +</g> +</svg> diff --git a/chapter11/pom.xml b/chapter11/pom.xml new file mode 100644 index 00000000..fb238068 --- /dev/null +++ b/chapter11/pom.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.seaofnodes</groupId> + <artifactId>simple</artifactId> + <version>1.0</version> + </parent> + <artifactId>chapter11</artifactId> + <packaging>jar</packaging> + <name>Chapter 11</name> +</project>
\ No newline at end of file diff --git a/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java new file mode 100644 index 00000000..054a489c --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -0,0 +1,238 @@ + package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.util.*; + +public abstract class GlobalCodeMotion { + + // Arrange that the existing isCFG() Nodes form a valid CFG. The + // Node.use(0) is always a block tail (either IfNode or head of the + // following block). There are no unreachable infinite loops. + public static void buildCFG( StopNode stop ) { + fixLoops(stop); + schedEarly(); + Parser.SCHEDULED = true; + schedLate(Parser.START); + } + + // ------------------------------------------------------------------------ + // Backwards walk on the CFG only, looking for unreachable code - which has + // to be an infinite loop. Insert a bogus never-taken exit to Stop, so the + // loop becomes reachable. Also, set loop nesting depth + private static void fixLoops(StopNode stop) { + // Backwards walk from Stop, looking for unreachable code + BitSet visit = new BitSet(); + HashSet<CFGNode> unreach = new HashSet<>(); + unreach.add(Parser.START); + for( Node ret : stop._inputs ) + ((ReturnNode)ret).walkUnreach(visit,unreach); + if( unreach.isEmpty() ) return; + + // Forwards walk from unreachable, looking for loops with no exit test. + visit.clear(); + for( CFGNode cfg : unreach ) + walkInfinite(cfg,visit,stop); + // Set loop depth on remaining graph + unreach.clear(); + visit.clear(); + for( Node ret : stop._inputs ) + ((ReturnNode)ret).walkUnreach(visit,unreach); + assert unreach.isEmpty(); + } + + + // Forwards walk over previously unreachable, looking for loops with no + // exit test. + static private void walkInfinite( CFGNode n, BitSet visit, StopNode stop ) { + if( visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + if( n instanceof LoopNode loop ) + loop.forceExit(stop); + for( Node use : n._outputs ) + if( use instanceof CFGNode cfg ) + walkInfinite(cfg,visit,stop); + } + + // ------------------------------------------------------------------------ + private static void schedEarly() { + ArrayList<CFGNode> rpo = new ArrayList<>(); + BitSet visit = new BitSet(); + _rpo_cfg(Parser.START, visit, rpo); + // Reverse Post-Order on CFG + for( int j=rpo.size()-1; j>=0; j-- ) { + CFGNode cfg = rpo.get(j); + cfg.loopDepth(); + for( Node n : cfg._inputs ) + _schedEarly(n,visit); + // Strictly for dead infinite loops, we can have entire code blocks + // not reachable from below - so we reach down, from above, one + // step. Since _schedEarly modifies the output arrays, the normal + // region._outputs ArrayList iterator throws CME. The extra edges + // are always *added* after any Phis, so just walk the Phi prefix. + if( cfg instanceof RegionNode region ) { + int len = region.nOuts(); + for( int i=0; i<len; i++ ) + if( region.out(i) instanceof PhiNode phi ) + _schedEarly(phi,visit); + } + } + } + + // Post-Order of CFG + private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) { + if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) + return; // Been there, done that + visit.set(cfg._nid); + for( Node use : cfg._outputs ) + _rpo_cfg(use,visit,rpo); + rpo.add(cfg); + } + + private static void _schedEarly(Node n, BitSet visit) { + if( n==null || visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + // Schedule not-pinned not-CFG inputs before self. Since skipping + // Pinned, this never walks the backedge of Phis (and thus spins around + // a data-only loop, eventually attempting relying on some pre-visited- + // not-post-visited data op with no scheduled control. + for( Node def : n._inputs ) + if( def!=null && !def.isPinned() ) + _schedEarly(def,visit); + // If not-pinned (e.g. constants, projections, phi) and not-CFG + if( !n.isPinned() ) { + // Schedule at deepest input + CFGNode early = Parser.START; // Maximally early, lowest idepth + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i).cfg0().idepth() > early.idepth() ) + early = n.in(i).cfg0(); // Latest/deepest input + n.setDef(0,early); // First place this can go + } + } + + // ------------------------------------------------------------------------ + private static void schedLate(StartNode start) { + CFGNode[] late = new CFGNode[Node.UID()]; + Node[] ns = new Node[Node.UID()]; + _schedLate(start,ns,late); + for( int i=0; i<late.length; i++ ) + if( ns[i] != null ) + ns[i].setDef(0,late[i]); + } + + // Forwards post-order pass. Schedule all outputs first, then draw a + // idom-tree line from the LCA of uses to the early schedule. Schedule is + // legal anywhere on this line; pick the most control-dependent (largest + // idepth) in the shallowest loop nest. + private static void _schedLate(Node n, Node[] ns, CFGNode[] late) { + if( late[n._nid]!=null ) return; // Been there, done that + // These I know the late schedule of, and need to set early for loops + if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); + if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); + + // Walk Stores before Loads, so we can get the anti-deps right + for( Node use : n._outputs ) + if( isForwardsEdge(use,n) && + use._type instanceof TypeMem ) + _schedLate(use,ns,late); + // Walk everybody now + for( Node use : n._outputs ) + if( isForwardsEdge(use,n) ) + _schedLate(use,ns,late); + // Already implicitly scheduled + if( n.isPinned() ) return; + // Need to schedule n + + // Walk uses, gathering the LCA (Least Common Ancestor) of uses + CFGNode early = (CFGNode)n.in(0); + assert early != null; + CFGNode lca = null; + for( Node use : n._outputs ) + lca = use_block(n,use, late).idom(lca); + + // Loads may need anti-dependencies, raising their LCA + if( n instanceof LoadNode load ) + lca = find_anti_dep(lca,load,early,late); + + // Walk up from the LCA to the early, looking for best place. This is + // lowest execution frequency, approximated by least loop depth and + // deepest control flow. + CFGNode best = lca; + lca = lca.idom(); // Already found best for starting LCA + for( ; lca != early.idom(); lca = lca.idom() ) + if( better(lca,best) ) + best = lca; + assert !(best instanceof IfNode); + ns [n._nid] = n; + late[n._nid] = best; + } + + // Block of use. Normally from late[] schedule, except for Phis, which go + // to the matching Region input. + private static CFGNode use_block(Node n, Node use, CFGNode[] late) { + if( !(use instanceof PhiNode phi) ) + return late[use._nid]; + CFGNode found=null; + for( int i=1; i<phi.nIns(); i++ ) + if( phi.in(i)==n ) + if( found==null ) found = phi.region().cfg(i); + else Utils.TODO(); // Can be more than once + assert found!=null; + return found; + } + + + // Least loop depth first, then largest idepth + private static boolean better( CFGNode lca, CFGNode best ) { + return lca._loopDepth < best._loopDepth || + (lca.idepth() > best.idepth() || best instanceof IfNode); + } + + // Skip iteration if a backedge + private static boolean isForwardsEdge(Node use, Node def) { + return use != null && def != null && + !(use.nIns()>2 && use.in(2)==def && (use instanceof LoopNode || (use instanceof PhiNode phi && phi.region() instanceof LoopNode))); + } + + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) { + // We could skip final-field loads here. + // Walk LCA->early, flagging Load's block location choices + for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) + cfg._anti = load._nid; + // Walk load->mem uses, looking for Stores causing an anti-dep + for( Node mem : load.mem()._outputs ) { + switch( mem ) { + case StoreNode st: + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + break; + case PhiNode phi: + // Repeat anti-dep for matching Phi inputs. + // No anti-dep edges but may raise the LCA. + for( int i=1; i<phi.nIns(); i++ ) + if( phi.in(i)==load.mem() ) + lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null); + break; + case LoadNode ld: break; // Loads do not cause anti-deps on other loads + case ReturnNode ret: break; // Load must already be ahead of Return + default: throw Utils.TODO(); + } + } + return lca; + } + + // + private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) { + // Walk store blocks "reach" from its scheduled location to its earliest + for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { + // Store and Load overlap, need anti-dependence + if( stblk._anti==load._nid ) { + lca = stblk.idom(lca); // Raise Loads LCA + if( lca == stblk && st != null && Utils.find(st._inputs,load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well + return lca; // Cap this stores' anti-dep to here + } + } + return lca; + } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java new file mode 100644 index 00000000..604770a4 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -0,0 +1,280 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.*; + +import java.util.*; + +/** + * Simple visualizer that outputs GraphViz dot format. + * The dot output must be saved to a file and run manually via dot to generate the SVG output. + * Currently, this is done manually. + */ +public class GraphVisualizer { + + /** + * If set to true we put the control nodes in a separate cluster from + * data nodes. + */ + boolean _separateControlCluster = false; + + public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } + public GraphVisualizer() { this(false); } + + public String generateDotOutput(Parser parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); } + public String generateDotOutput(StopNode stop, Node scope, Stack<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(stop, scope); + StringBuilder sb = new StringBuilder(); + sb.append("digraph chapter11 {\n"); + sb.append("/*\n"); + sb.append(stop._src); + sb.append("\n*/\n"); + + // To keep the Scopes below the graph and pointing up into the graph we + // need to group the Nodes in a subgraph cluster, and the scopes into a + // different subgraph cluster. THEN we can draw edges between the + // scopes and nodes. If we try to cross subgraph cluster borders while + // still making the subgraphs DOT gets confused. + sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes + + // CNC - turned off Apr/8/2024, gives more flex in the layout and + // removes some of the more ludicrous layout choices. + // Preserve node input order + //sb.append("\tordering=\"in\";\n"); + + // Merge multiple edges hitting the same node. Makes common shared + // nodes much prettier to look at. + sb.append("\tconcentrate=\"true\";\n"); + + // Force nested scopes to order + sb.append("\tcompound=\"true\";\n"); + + // Just the Nodes first, in a cluster no edges + nodes(sb, all); + + // Now the scopes, in a cluster no edges + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scope( sb, xscope ); + + // Walk the Node edges + nodeEdges(sb, all); + + // Walk the active Scope edges + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scopeEdges( sb, xscope ); + + sb.append("}\n"); + return sb.toString(); + } + + private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) { + if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster + return; + // Just the Nodes first, in a cluster no edges + sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name + for (Node n : all) { + if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL) + continue; // Do not emit, rolled into MultiNode or Scope cluster already + if (_separateControlCluster && doCtrl && !n.isCFG()) continue; + if (_separateControlCluster && !doCtrl && n.isCFG()) continue; + sb.append("\t\t").append(n.uniqueName()).append(" [ "); + String lab = n.glabel(); + if (n instanceof MultiNode) { + // Make a box with the MultiNode on top, and all the projections on the bottom + sb.append("shape=plaintext label=<\n"); + sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"); + sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n"); + sb.append("\t\t\t<TR>"); + boolean doProjTable = false; + for (Node use : n._outputs) { + if (use instanceof ProjNode proj) { + if (!doProjTable) { + doProjTable = true; + sb.append("<TD>").append("\n"); + sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n"); + sb.append("\t\t\t\t<TR>"); + } + sb.append("<TD PORT=\"p").append(proj._idx).append("\">").append(proj.glabel()).append("</TD>"); + } + if (use instanceof CProjNode proj) { + if (!doProjTable) { + doProjTable = true; + sb.append("<TD>").append("\n"); + sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n"); + sb.append("\t\t\t\t<TR>"); + } + sb.append("<TD PORT=\"p").append(proj._idx).append("\" BGCOLOR=\"yellow\">").append(proj.glabel()).append("</TD>"); + } + } + if (doProjTable) { + sb.append("</TR>").append("\n"); + sb.append("\t\t\t\t</TABLE>").append("\n"); + sb.append("\t\t\t</TD>"); + } + sb.append("</TR>\n"); + sb.append("\t\t\t</TABLE>>\n\t\t"); + + } else { + // control nodes have box shape + // other nodes are ellipses, i.e. default shape + if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow "); + else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); + sb.append("label=\"").append(lab).append("\" "); + } + sb.append("];\n"); + } + if (!_separateControlCluster) { + // Force Region & Phis to line up + for (Node n : all) { + if (n instanceof RegionNode region) { + sb.append("\t\t{ rank=same; "); + sb.append(region).append(";"); + for (Node phi : region._outputs) + if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); + sb.append("}\n"); + } + } + } + sb.append("\t}\n"); // End Node cluster + } + + private void nodes(StringBuilder sb, Collection<Node> all) { + nodesByCluster(sb, true, all); + nodesByCluster(sb, false, all); + } + + // Build a nested scope display, walking the _prev edge + private void scope( StringBuilder sb, ScopeNode scope ) { + sb.append("\tnode [shape=plaintext];\n"); + int level=1; + for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) { + var syms = scope._scopes.get(idx); + String scopeName = makeScopeName(scope, level); + sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name + sb.append("\t\t").append(scopeName).append(" [label=<\n"); + sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"); + // Add the scope level + int scopeLevel = scope._scopes.size()-level; + sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(scopeLevel).append("</TD>"); + for(String name: syms.keySet()) + sb.append("<TD PORT=\"").append(makePortName(scopeName, name)).append("\">").append(name).append("</TD>"); + sb.append("</TR>\n"); + sb.append("\t\t\t</TABLE>>];\n"); + level++; + } + // Scope clusters nest, so the graphics shows the nested scopes, so + // they are not closed as they are printed; so they just keep nesting. + // We close them all at once here. + sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters + } + + private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } + private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } + + // Walk the node edges + private void nodeEdges(StringBuilder sb, Collection<Node> all) { + // All them edge labels + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); + for( Node n : all ) { + // Do not display the Constant->Start edge; + // ProjNodes handled by Multi; + // ScopeNodes are done separately + if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) + continue; + for( int i=0; i<n.nIns(); i++ ) { + Node def = n.in(i); + if( n instanceof PhiNode && def instanceof RegionNode ) { + // Draw a dotted use->def edge from Phi to Region. + sb.append('\t').append(n.uniqueName()); + sb.append(" -> "); + sb.append(def.uniqueName()); + sb.append(" [style=dotted taillabel=").append(i).append("];\n"); + } else if( def != null ) { + // Most edges land here use->def + sb.append('\t').append(n.uniqueName()).append(" -> "); + if( def instanceof CProjNode proj ) { + String mname = proj.ctrl().uniqueName(); + sb.append(mname).append(":p").append(proj._idx); + } else if( def instanceof ProjNode proj ) { + String mname = proj.in(0).uniqueName(); + sb.append(mname).append(":p").append(proj._idx); + } else sb.append(def.uniqueName()); + // Number edges, so we can see how they track + sb.append("[taillabel=").append(i); + // The edge from New to ctrl is just for anchoring the New + if ( n instanceof NewNode ) + sb.append(" color=green"); + // control edges are colored red + else if( def.isCFG() ) + sb.append(" color=red"); + else if( def.isMem() ) + sb.append(" color=blue"); + // Backedges do not add a ranking constraint + if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) + sb.append(" constraint=false"); + sb.append("];\n"); + } + } + } + } + + // Walk the scope edges + private void scopeEdges( StringBuilder sb, ScopeNode scope ) { + sb.append("\tedge [style=dashed color=cornflowerblue];\n"); + int level=1; + for( int i = scope._scopes.size()-1; i>=0; i-- ) { + var syms = scope._scopes.get(i); + String scopeName = makeScopeName(scope, level); + for( String name : syms.keySet() ) { + int idx = syms.get(name); + Node def = scope.in(idx); + while( def instanceof ScopeNode lazy ) + def = lazy.in(idx); + if( def==null ) continue; + sb.append("\t") + .append(scopeName).append(":") + .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted + .append(" -> "); + if( def instanceof CProjNode proj ) { + String mname = proj.ctrl().uniqueName(); + sb.append(mname).append(":p").append(proj._idx); + } else sb.append(def.uniqueName()); + sb.append(";\n"); + } + level++; + } + } + + /** + * Finds all nodes in the graph. + */ + private Collection<Node> findAll(Node stop, Node scope) { + final HashMap<Integer, Node> all = new HashMap<>(); + for( Node n : stop._inputs ) + walk(all, n); + if( scope != null ) + for( Node n : scope._inputs ) + walk(all, n); + return all.values(); + } + + /** + * Walk a subgraph and populate distinct nodes in the all list. + */ + private void walk(HashMap<Integer,Node> all, Node n) { + if(n == null ) return; + if (all.get(n._nid) != null) return; // Been there, done that + all.put(n._nid, n); + for (Node c : n._inputs) + walk(all, c); + for( Node c : n._outputs ) + walk(all, c); + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter11/src/main/java/com/seaofnodes/simple/IRPrinter.java new file mode 100644 index 00000000..43c6c9f7 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/IRPrinter.java @@ -0,0 +1,266 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import java.lang.StringBuilder; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; + +public class IRPrinter { + + // Print a node on 1 line, columnar aligned, as: + // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE + // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt + public static void _printLine( Node n, StringBuilder sb ) { + sb.append("%4d %-7.7s ".formatted(n._nid,n.label())); + if( n._inputs==null ) { + sb.append("DEAD\n"); + return; + } + for( Node def : n._inputs ) + sb.append(def==null ? "____ " : "%4d ".formatted(def._nid)); + for( int i = n._inputs.size(); i<4; i++ ) + sb.append(" "); + sb.append(" [[ "); + for( Node use : n._outputs ) + sb.append(use==null ? "____ " : "%4d ".formatted(use._nid)); + int lim = 6 - Math.max(n._inputs.size(),4); + for( int i = n._outputs.size(); i<lim; i++ ) + sb.append(" "); + sb.append(" ]] "); + if( n._type!= null ) sb.append(n._type.str()); + sb.append("\n"); + } + + private static StringBuilder nodeId(StringBuilder sb, Node n) { + sb.append("%%%d".formatted(n._nid)); + if (n instanceof ProjNode proj) { + sb.append(".").append(proj._idx); + } + return sb; + } + + // Print a node on 1 line, format is inspired by LLVM + // %id: TYPE = NODE(inputs ....) + // Nodes as referred to as %id + public static void _printLineLlvmFormat( Node n, StringBuilder sb ) { + nodeId(sb, n).append(": "); + if( n._inputs==null ) { + sb.append("DEAD\n"); + return; + } + if( n._type!= null ) n._type.typeName(sb); + sb.append(" = ").append( n.label() ).append( "(" ); + for( int i = 0; i < n._inputs.size(); i++ ) { + Node def = n.in(i); + if (i > 0) + sb.append(", "); + if (def == null) sb.append("_"); + else nodeId(sb, def); + } + sb.append(")").append("\n"); + } + + public static void printLine( Node n, StringBuilder sb, boolean llvmFormat ) { + if (llvmFormat) _printLineLlvmFormat( n, sb ); + else _printLine ( n, sb ); + } + + public static String prettyPrint(Node node, int depth) { + return Parser.SCHEDULED + ? prettyPrintScheduled( node, depth, false ) + : prettyPrint( node, depth, false ); + } + + // Another bulk pretty-printer. Makes more effort at basic-block grouping. + public static String prettyPrint( Node node, int depth, boolean llvmFormat ) { + // First, a Breadth First Search at a fixed depth. + BFS bfs = new BFS(node,depth); + // Convert just that set to a post-order + ArrayList<Node> rpos = new ArrayList<>(); + BitSet visit = new BitSet(); + for( int i=bfs._lim; i< bfs._bfs.size(); i++ ) + postOrd( bfs._bfs.get(i), rpos, visit, bfs._bs); + // Reverse the post-order walk + StringBuilder sb = new StringBuilder(); + boolean gap=false; + for( int i=rpos.size()-1; i>=0; i-- ) { + Node n = rpos.get(i); + if( n.isCFG() || n.isMultiHead() ) { + if( !gap ) sb.append("\n"); // Blank before multihead + printLine( n, sb, llvmFormat ); // Print head + while( --i >= 0 ) { + Node t = rpos.get(i); + if( !t.isMultiTail() ) { i++; break; } + printLine( t, sb, llvmFormat ); + } + sb.append("\n"); // Blank after multitail + gap = true; + } else { + printLine( n, sb, llvmFormat ); + gap = false; + } + } + return sb.toString(); + } + + private static void postOrd(Node n, ArrayList<Node> rpos, BitSet visit, BitSet bfs) { + if( !bfs.get(n._nid) ) + return; // Not in the BFS visit + if( visit.get(n._nid) ) return; // Already post-order walked + visit.set(n._nid); + // First walk the CFG, then everything + if( n.isCFG() ) { + for( Node use : n._outputs ) + 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!=null && use.isCFG() ) + postOrd(use,rpos,visit,bfs); + } + for( Node use : n._outputs ) + if( use != null ) + postOrd(use, rpos,visit,bfs); + // Post-order + rpos.add(n); + } + + // Breadth-first search, broken out in a class to keep in more independent. + // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead). + public static class BFS { + // A breadth first search, plus MultiHeads for any MultiTails + public final ArrayList<Node> _bfs; + public final BitSet _bs; // Visited members by node id + public final int _depth; // Depth limit + public final int _lim; // From here to _bfs._len can be roots for a reverse search + public BFS( Node base, int d ) { + _depth = d; + _bfs = new ArrayList<>(); + _bs = new BitSet(); + + add(base); // Prime the pump + int idx=0, lim=1; // Limit is where depth counter changes + while( idx < _bfs.size() ) { // Ran out of nodes below depth + Node n = _bfs.get(idx++); + for( Node def : n._inputs ) + if( def!=null && !_bs.get(def._nid) ) + add(def); + if( idx==lim ) { // Depth counter changes at limit + if( --d < 0 ) + break; // Ran out of depth + lim = _bfs.size(); // New depth limit + } + } + // Toss things past the limit except multi-heads + while( idx < _bfs.size() ) { + Node n = _bfs.get(idx); + if( n.isMultiHead() ) idx++; + else del(idx); + } + // Root set is any node with no inputs in the visited set + lim = _bfs.size(); + for( int i=_bfs.size()-1; i>=0; i-- ) + if( !any_visited(_bfs.get(i)) ) + swap( i,--lim); + _lim = lim; + } + void swap( int x, int y ) { + if( x==y ) return; + Node tx = _bfs.get(x); + Node ty = _bfs.get(y); + _bfs.set(x,ty); + _bfs.set(y,tx); + } + void add(Node n) { + _bfs.add(n); + _bs.set(n._nid); + } + void del(int idx) { + Node n = Utils.del(_bfs, idx); + _bs.clear(n._nid); + } + boolean any_visited( Node n ) { + for( Node def : n._inputs ) + if( def!=null && _bs.get(def._nid) ) + return true; + return false; + } + } + + // Bulk pretty printer, knowing scheduling information is available + public static String prettyPrintScheduled( Node node, int depth, boolean llvmFormat ) { + // Backwards DFS walk to depth. + HashMap<Node,Integer> ds = new HashMap<>(); + _walk(ds,node,depth); + // Print by block with least idepth + StringBuilder sb = new StringBuilder(); + ArrayList<Node> bns = new ArrayList<>(); + while( !ds.isEmpty() ) { + CFGNode blk = null; + for( Node n : ds.keySet() ) { + CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : (CFGNode)n.in(0); + if( blk==null || cfg.idepth() < blk.idepth() ) + blk = cfg; + } + ds.remove(blk); + + // Print block header + sb.append("%-13.13s".formatted(label(blk)+":")); + sb.append( " ".repeat(4) ).append(" [[ "); + if( blk instanceof RegionNode || blk instanceof StopNode ) + for( int i=(blk instanceof StopNode ? 0 : 1); i<blk.nIns(); i++ ) + label(sb,blk.cfg(i)); + else if( !(blk instanceof StartNode) ) + label(sb,blk.cfg(0)); + sb.append(" ]] \n"); + + // Collect block contents that are in the depth limit + bns.clear(); + int xd = Integer.MAX_VALUE; + for( Node use : blk._outputs ) { + Integer i = ds.get(use); + if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) { + bns.add(use); + xd = Math.min(xd,i); + } + } + // Print Phis up front, if any + for( int i=0; i<bns.size(); i++ ) + if( bns.get(i) instanceof PhiNode phi ) + printLine( phi, sb, llvmFormat,bns,i--,ds); + + // Print block contents in depth order, bumping depth until whole block printed + for( ; !bns.isEmpty(); xd++ ) + for( int i=0; i<bns.size(); i++ ) + if( ds.get(bns.get(i))==xd ) + printLine( bns.get(i), sb, llvmFormat, bns, i--, ds ); + sb.append("\n"); + } + return sb.toString(); + } + + private static void _walk( HashMap<Node,Integer> ds, Node node, int d ) { + Integer nd = ds.get(node); + if( nd!=null && d <= nd ) return; // Been there, done that + ds.put(node,d); + if( d == 0 ) return; // Depth cutoff + for( Node def : node._inputs ) + if( def != null ) + _walk(ds,def,d-1); + } + + static String label( CFGNode blk ) { + if( blk instanceof StartNode ) return "START"; + return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid; + } + static void label( StringBuilder sb, CFGNode blk ) { + if( !blk.blockHead() ) blk = blk.cfg(0); + sb.append( "%-9.9s ".formatted( label( blk ) ) ); + } + static void printLine( Node n, StringBuilder sb, boolean llvmFormat, ArrayList<Node> bns, int i, HashMap<Node,Integer> ds ) { + printLine( n, sb, llvmFormat ); + Utils.del(bns,i); + ds.remove(n); + } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java new file mode 100644 index 00000000..b2b76777 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -0,0 +1,205 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Random; + +/** + * The IterOptim runs after parsing. It iterates the peepholes to a fixed point + * so that no more peepholes apply. This should be linear because peepholes rarely + * (never?) increase code size. The graph should monotonically reduce in some + * dimension, which is usually size. It might also reduce in e.g. number of + * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper + * ones. + * + * The theoretical overall worklist is mindless just grabbing the next thing and + * doing it. If the graph changes, put the neighbors on the worklist. Lather, + * Rinse, Repeat until the worklist runs dry. + * + * The main issues we have to deal with: + * + * <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( n.isUnused() && !(n instanceof StopNode) ) + n.kill(); // Just plain dead + } + + if( show ) + System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null)); + GlobalCodeMotion.buildCFG(stop); + 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/chapter11/src/main/java/com/seaofnodes/simple/Parser.java b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java new file mode 100644 index 00000000..523c72de --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java @@ -0,0 +1,844 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +import java.util.*; + +/** + * The Parser converts a Simple source program to the Sea of Nodes intermediate + * representation directly in one pass. There is no intermediate Abstract + * Syntax Tree structure. + * <p> + * This is a simple recursive descent parser. All lexical analysis is done here as well. + */ +public class Parser { + + /** + * A Global Static, unique to each compilation. This is a public, so we + * can make constants everywhere without having to thread the StartNode + * through the entire parser and optimizer. + * <p> + * To make the compiler multithreaded, this field will have to move into a TLS. + */ + public static StartNode START; + + public static ConstantNode ZERO; + public static XCtrlNode XCTRL; + + public StopNode STOP; + + // Debugger Printing. + public static boolean SCHEDULED; // True if debug printer can use schedule info + + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. + private final Lexer _lexer; + + /** + * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time + * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how + * we handle branching. See {@link #parseIf()}. + * <p> + * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds + * variable names to Nodes. The top of this stack represents current scope. + * <p> + * We keep a list of all ScopeNodes so that we can show them in graphs. + * @see #parseIf() + * @see #_xScopes + */ + public ScopeNode _scope; + + /** + * List of keywords disallowed as identifiers + */ + private static final HashSet<String> KEYWORDS = new HashSet<>(){{ + add("break"); + add("continue"); + add("else"); + add("false"); + add("if"); + add("int"); + add("new"); + add("null"); + add("return"); + add("struct"); + add("true"); + add("while"); + }}; + + + /** + * We clone ScopeNodes when control flows branch; it is useful to have + * a list of all active ScopeNodes for purposes of visualization of the SoN graph + */ + public final Stack<ScopeNode> _xScopes = new Stack<>(); + + ScopeNode _continueScope; + ScopeNode _breakScope; + + public static Map<String, TypeStruct> OBJS = new HashMap<>(); + + public Parser(String source, TypeInteger arg) { + Node.reset(); + IterPeeps.reset(); + Type.reset(); + OBJS.clear(); + SCHEDULED = false; + _lexer = new Lexer(source); + _scope = new ScopeNode(); + _continueScope = _breakScope = null; + START = new StartNode(new Type[]{ Type.CONTROL, arg }); + STOP = new StopNode(source); + ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); + XCTRL= new XCtrlNode().peephole().keep(); + } + + public Parser(String source) { + this(source, TypeInteger.BOT); + } + + @Override + public String toString() { return _lexer.toString(); } + + String src() { return new String( _lexer._input ); } + + // Debugging utility to find a Node by index + public static Node find(int nid) { return START.find(nid); } + + private Node ctrl() { return _scope.ctrl(); } + + private Node ctrl(Node n) { return _scope.ctrl(n); } + + public StopNode parse() { return parse(false); } + public StopNode parse(boolean show) { + _xScopes.push(_scope); + // Enter a new scope for the initial control and arguments + _scope.push(); + _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 1, ScopeNode.ARG0).peephole()); + parseBlock(); + if( ctrl()._type==Type.CONTROL ) + STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); + _scope.pop(); + _xScopes.pop(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); + STOP.peephole(); + if( show ) showGraph(); + return STOP; + } + + /** + * Parses a block + * + * <pre> + * '{' statements '}' + * </pre> + * Does not parse the opening or closing '{}' + * @return a {@link Node} or {@code null} + */ + private Node parseBlock() { + // Enter a new scope + _scope.push(); + while (!peek('}') && !_lexer.isEOF()) + parseStatement(); + // Exit scope + _scope.pop(); + return null; + } + + /** + * Parses a statement + * + * <pre> + * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement + * </pre> + * @return a {@link Node} or {@code null} + */ + private Node parseStatement() { + if( false ) return null; + else if (matchx("return") ) return parseReturn(); + else if (matchx("int") ) return parseDecl(TypeInteger.BOT); + else if (match ("{") ) return require(parseBlock(),"}"); + else if (matchx("if") ) return parseIf(); + else if (matchx("while") ) return parseWhile(); + else if (matchx("break") ) return parseBreak(); + else if (matchx("continue")) return parseContinue(); + else if (matchx("struct") ) return parseStruct(); + else if (matchx("#showGraph")) return require(showGraph(),";"); + // declarations of vars with struct type are handled in parseExpressionStatement due + // to ambiguity + else return parseExpressionStatement(); + } + + /** + * Parse a struct field. + * <pre> + * int IDENTIFIER ; + * </pre> + */ + private Field parseField(String sname) { + if( matchx("int") ) // Currently only parsing "int" type fields + return require(Field.make(sname,TypeInteger.BOT,requireId()),";"); + throw errorSyntax("A field type is expected, only type 'int' is supported at present"); + } + + /** + * Parse a struct declaration, and return the following statement. + * Only allowed in top level scope. + * Structs cannot be redefined. + * + * @return The statement following the struct + */ + private Node parseStruct() { + if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + String typeName = requireId(); + if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + ArrayList<Field> fields = new ArrayList<>(); + require("{"); + while (!peek('}') && !_lexer.isEOF()) { + Field field = parseField(typeName); + if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); + fields.add(field); + } + require("}"); + // Build and install the TypeStruct + TypeStruct ts = TypeStruct.make(typeName, fields); + OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names + START.addMemProj(ts, _scope); // Insert memory edges + return parseStatement(); + } + + /** + * Parses a while statement + * + * <pre> + * while ( expression ) statement + * </pre> + * @return a {@link Node}, never {@code null} + */ + private Node parseWhile() { + + var savedContinueScope = _continueScope; + var savedBreakScope = _breakScope; + + require("("); + + // Loop region has two control inputs, the first is the entry + // point, and second is back edge that is set after loop is parsed + // (see end_loop() call below). Note that the absence of back edge is + // used as an indicator to switch off peepholes of the region and + // associated phis; see {@code inProgress()}. + + ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here + + // At loop head, we clone the current Scope (this includes all + // names in every nesting level within the Scope). + // We create phis eagerly for all the names we find, see dup(). + + // Save the current scope as the loop head + ScopeNode head = _scope.keep(); + // Clone the head Scope to create a new Scope for the body. + // Create phis eagerly as part of cloning + _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis + + // Parse predicate + var pred = require(parseExpression(), ")"); + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + + // Clone the body Scope to create the break/exit Scope which accounts for any + // side effects in the predicate. The break/exit Scope will be the final + // scope after the loop, and its control input is the False branch of + // the loop predicate. Note that body Scope is still our current scope. + ctrl(ifF); + _xScopes.push(_breakScope = _scope.dup()); + + // No continues yet + _continueScope = null; + + // Parse the true side, which corresponds to loop body + // Our current scope is the body Scope + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + parseStatement(); // Parse loop body + + // Merge the loop bottom into other continue statements + if (_continueScope != null) { + _continueScope = jumpTo(_continueScope); + _scope.kill(); + _scope = _continueScope; + } + + // The true branch loops back, so whatever is current _scope.ctrl gets + // added to head loop as input. endLoop() updates the head scope, and + // goes through all the phis that were created earlier. For each phi, + // it sets the second input to the corresponding input from the back + // edge. If the phi is redundant, it is replaced by its sole input. + var exit = _breakScope; + head.endLoop(_scope, exit); + head.unkeep().kill(); + + _xScopes.pop(); // Cleanup + _xScopes.pop(); // Cleanup + + _continueScope = savedContinueScope; + _breakScope = savedBreakScope; + + // At exit the false control is the current control, and + // the scope is the exit scope after the exit test. + return _scope = exit; + } + + private ScopeNode jumpTo(ScopeNode toScope) { + ScopeNode cur = _scope.dup(); + ctrl(XCTRL); // Kill current scope + // Prune nested lexical scopes that have depth > than the loop head + // We use _breakScope as a proxy for the loop head scope to obtain the depth + while( cur._scopes.size() > _breakScope._scopes.size() ) + cur.pop(); + // If this is a continue then first time the target is null + // So we just use the pruned current scope as the base for the + // continue + if (toScope == null) + return cur; + // toScope is either the break scope, or a scope that was created here + assert toScope._scopes.size() <= _breakScope._scopes.size(); + toScope.ctrl(toScope.mergeScopes(cur)); + return toScope; + } + + private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } + + private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); } + private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } + + /** + * Parses a statement + * + * <pre> + * if ( expression ) statement [else statement] + * </pre> + * @return a {@link Node}, never {@code null} + */ + private Node parseIf() { + require("("); + // Parse predicate + var pred = require(parseExpression(), ")").keep(); + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + // In if true branch, the ifT proj node becomes the ctrl + // But first clone the scope and set it as current + int ndefs = _scope.nIns(); + ScopeNode fScope = _scope.dup(); // Duplicate current scope + _xScopes.push(fScope); // For graph visualization we need all scopes + + // Parse the true side + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.upcast(ifT,pred,false); // Up-cast predicate + parseStatement(); // Parse true-side + ScopeNode tScope = _scope; + + // Parse the false side + _scope = fScope; // Restore scope, then parse else block if any + ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + if (matchx("else")) { + _scope.upcast(ifF,pred,true); // Up-cast predicate + parseStatement(); + fScope = _scope; + } + pred.unkeep(); + + if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) + throw error("Cannot define a new name on one arm of an if"); + + // Merge results + _scope = tScope; + _xScopes.pop(); // Discard pushed from graph display + + return ctrl(tScope.mergeScopes(fScope)); + } + + + /** + * Parses a return statement; "return" already parsed. + * The $ctrl edge is killed. + * + * <pre> + * 'return' expr ; + * </pre> + * @return an expression {@link Node}, never {@code null} + */ + private Node parseReturn() { + var expr = require(parseExpression(), ";"); + Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); + ctrl(XCTRL); // Kill control + return ret; + } + + /** + * Dumps out the node graph + * @return {@code null} + */ + Node showGraph() { + System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes)); + return null; + } + + /** + * Parses an expression statement or a declaration statement where type is a struct + * + * <pre> + * name; // Error + * type name; // Define name with default initial value + * type name = expr; // Define name with given initial value + * name = expr; // Reassign existing + * expr // Something else + * </pre> + * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpressionStatement() { + int old = _lexer._position; + Type t = type(); + String name = requireId(); + Node expr; + if( match(";") ) { // Assign a default value + // No type and no expr is an error + if( t==null ) throw errorSyntax("expression"); + expr = new ConstantNode(t.makeInit()).peephole(); + } else if( match("=") ) { // Assign "= expr;" + expr = require(parseExpression(), ";"); + } else { // Neither, so just a normal expression parse + _lexer._position = old; + return require(parseExpression(),";"); + } + + // Defining a new variable vs updating an old one + if( t != null ) { + if( _scope.define(name,t,expr) == null ) + throw error("Redefining name '" + name + "'"); + } else { + Node n = _scope.lookup(name); + t = _scope.lookupDeclaredType(name); + if( n==null ) + throw error("Undefined name '" + name + "'"); + _scope.update(name,expr); + } + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + return expr; + } + + // Parse a type-or-null + private Type type() { + int old = _lexer._position; + String tname = _lexer.matchId(); + if( tname==null ) return null; + if( tname.equals("int") ) return TypeInteger.BOT; + TypeStruct obj = OBJS.get(tname); + if( obj != null ) + return TypeMemPtr.make(obj,match("?")); + // Not a type; unwind the parse + _lexer._position = old; + return null; + } + + /** + * Parses a declStatement + * + * <pre> + * type name = expression ';' + * </pre> + * @return an expression {@link Node} + */ + private Node parseDecl(Type t) { + var name = requireId(); + var expr = match(";") + // Assign a null value + ? new ConstantNode(t.makeInit()).peephole() + // Assign "= expr;" + : require(require("=").parseExpression(), ";"); + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + if( _scope.define(name,t,expr) == null ) + throw error("Redefining name '" + name + "'"); + return expr; + } + + + /** + * Parse an expression of the form: + * + * <pre> + * expr : compareExpr + * </pre> + * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpression() { return parseComparison(); } + + /** + * Parse an expression of the form: + * + * <pre> + * expr : additiveExpr op additiveExpr + * </pre> + * @return an comparator expression {@link Node}, never {@code null} + */ + private Node parseComparison() { + var lhs = parseAddition(); + while( true ) { + int idx=0; boolean negate=false; + // Test for any local nodes made, and "keep" lhs during peepholes + if( false ) ; + else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); } + else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; } + else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); } + else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); } + else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); } + else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); } + else break; + // Peepholes can fire, but lhs is already "hooked", kept alive + lhs.setDef(idx,parseAddition()); + lhs = lhs.peephole(); + if( negate ) // Extra negate for != + lhs = new NotNode(lhs).peephole(); + } + return lhs; + } + + /** + * Parse an additive expression + * + * <pre> + * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)* + * </pre> + * @return an add expression {@link Node}, never {@code null} + */ + private Node parseAddition() { + Node lhs = parseMultiplication(); + while( true ) { + if( false ) ; + else if( match("+") ) lhs = new AddNode(lhs,null); + else if( match("-") ) lhs = new SubNode(lhs,null); + else break; + lhs.setDef(2,parseMultiplication()); + lhs = lhs.peephole(); + } + return lhs; + } + + /** + * Parse an multiplicativeExpr expression + * + * <pre> + * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)* + * </pre> + * @return a multiply expression {@link Node}, never {@code null} + */ + private Node parseMultiplication() { + var lhs = parseUnary(); + while( true ) { + if( false ) ; + else if( match("*") ) lhs = new MulNode(lhs,null); + else if( match("/") ) lhs = new DivNode(lhs,null); + else break; + lhs.setDef(2,parseUnary()); + lhs = lhs.peephole(); + } + return lhs; + } + + /** + * Parse a unary minus expression. + * + * <pre> + * unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr + * </pre> + * @return a unary expression {@link Node}, never {@code null} + */ + private Node parseUnary() { + if (match("-")) return new MinusNode(parseUnary()).peephole(); + if (match("!")) return new NotNode(parseUnary()).peephole(); + return parsePostfix(parsePrimary()); + } + + /** + * Parse a primary expression: + * + * <pre> + * primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')' + * </pre> + * @return a primary {@link Node}, never {@code null} + */ + private Node parsePrimary() { + if( _lexer.isNumber() ) return parseIntegerLiteral(); + if( match("(") ) return require(parseExpression(), ")"); + if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); + if( matchx("false") ) return ZERO; + if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); + if( matchx("new") ) { + String structName = requireId(); + TypeStruct obj = OBJS.get(structName); + if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'"); + return newStruct(obj); + } + String name = _lexer.matchId(); + if( name == null) throw errorSyntax("an identifier or expression"); + Node n = _scope.lookup(name); + if( n!=null ) return n; + throw error("Undefined name '" + name + "'"); + } + + /** + * Return a NewNode but also generate instructions to initialize it. + */ + private Node newStruct(TypeStruct obj) { + Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); + for( Field field: obj._fields ) + memAlias(field, new StoreNode(field, memAlias(field), n, ZERO).peephole()); + return n.unkeep(); + } + + // We set up memory aliases by inserting special vars in the scope these + // variables are prefixed by $ so they cannot be referenced in Simple code. + // Using vars has the benefit that all the existing machinery of scoping + // and phis work as expected + private Node memAlias(Field fld ) { return _scope.lookup(fld.aliasName() ); } + private Node memAlias(Field fld, Node st) { return _scope.update(fld.aliasName(), st); } + + /** + * Parse postfix expression. For now this is just a field + * expression, but in future could be array index too. + * + * <pre> + * expr ('.' IDENTIFIER)* [ = expr ] + * </pre> + */ + private Node parsePostfix(Node expr) { + if( !match(".") ) return expr; + + if( !(expr._type instanceof TypeMemPtr ptr) ) + throw error("Expected struct reference but got " + expr._type.str()); + + String name = requireId(); + Field field = ptr._obj==null ? null : ptr._obj.get(name); + if( field == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + field = field.glb(); // Even if the parser knows the field is a high type, use the low one + + if( match("=") ) { + // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment + if( peek('=') ) _lexer._position--; + else { + Node val = parseExpression(); + memAlias(field, new StoreNode(field, memAlias(field), expr, val).peephole()); + return expr; // "obj.a = expr" returns the expression while updating memory + } + } + + return parsePostfix(new LoadNode(field, memAlias(field), expr).peephole()); + } + + /** + * Parse integer literal + * + * <pre> + * integerLiteral: [1-9][0-9]* | [0] + * </pre> + */ + private ConstantNode parseIntegerLiteral() { + return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); + } + + ////////////////////////////////// + // Utilities for lexical analysis + + // Return true and skip if "syntax" is next in the stream. + private boolean match (String syntax) { return _lexer.match (syntax); } + // Match must be "exact", not be followed by more id letters + private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + // Return true and do NOT skip if 'ch' is next + private boolean peek(char ch) { return _lexer.peek(ch); } + private boolean peekIsId() { return _lexer.peekIsId(); } + + // Require and return an identifier + private String requireId() { + String id = _lexer.matchId(); + if (id != null && !KEYWORDS.contains(id) ) return id; + throw error("Expected an identifier, found '"+id+"'"); + } + + + + // Require an exact match + private Parser require(String syntax) { require(null, syntax); return this; } + private <N> N require(N n, String syntax) { + if (match(syntax)) return n; + throw errorSyntax(syntax); + } + + RuntimeException errorSyntax(String syntax) { + return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); + } + + public static RuntimeException error( String errorMessage ) { + return new RuntimeException(errorMessage); + } + + //////////////////////////////////// + // Lexer components + + private static class Lexer { + + // Input buffer; an array of text bytes read from a file or a string + private final byte[] _input; + // Tracks current position in input buffer + private int _position = 0; + + /** + * Record the source text for lexing + */ + public Lexer(String source) { + this(source.getBytes()); + } + + /** + * Direct from disk file source + */ + public Lexer(byte[] buf) { + _input = buf; + } + + // Very handy in the debugger, shows the unparsed program + @Override + public String toString() { + return new String(_input, _position, _input.length - _position); + } + + // True if at EOF + private boolean isEOF() { + return _position >= _input.length; + } + + // Peek next character, or report EOF + private char peek() { + return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate + : (char) _input[_position]; + } + + private char nextChar() { + char ch = peek(); + _position++; + return ch; + } + + // True if a white space + private boolean isWhiteSpace() { + return peek() <= ' '; // Includes all the use space, tab, newline, CR + } + + /** + * Return the next non-white-space character + */ + private void skipWhiteSpace() { + while( true ) { + if( isWhiteSpace() ) _position++; + // Skip // to end of line + else if( _position+2 < _input.length && + _input[_position ] == '/' && + _input[_position+1] == '/') { + _position += 2; + while( !isEOF() && _input[_position] != '\n' ) _position++; + } else break; + } + } + + + // Return true, if we find "syntax" after skipping white space; also + // then advance the cursor past syntax. + // Return false otherwise, and do not advance the cursor. + boolean match(String syntax) { + skipWhiteSpace(); + int len = syntax.length(); + if (_position + len > _input.length) return false; + for (int i = 0; i < len; i++) + if ((char) _input[_position + i] != syntax.charAt(i)) + return false; + _position += len; + return true; + } + + // Match must be exact and not followed by more ID characters. + // Prevents identifier "ifxy" from matching an "if" statement. + boolean matchx(String syntax) { + if( !match(syntax) ) return false; + if( !isIdLetter(peek()) ) return true; + _position -= syntax.length(); + return false; + } + + private boolean peek(char ch) { + skipWhiteSpace(); + return peek()==ch; + } + + boolean peekIsId() { + skipWhiteSpace(); + return isIdStart(peek()); + } + + // Return an identifier or null + String matchId() { + return peekIsId() ? parseId() : null; + } + + // Used for errors + String getAnyNextToken() { + if (isEOF()) return ""; + if (isIdStart(peek())) return parseId(); + if (isPunctuation(peek())) return parsePunctuation(); + return String.valueOf(peek()); + } + + boolean isNumber() {return isNumber(peek());} + boolean isNumber(char ch) {return Character.isDigit(ch);} + + private Type parseNumber() { + int start = _position; + while (isNumber(nextChar())) ; + String snum = new String(_input, start, --_position - start); + if (snum.length() > 1 && snum.charAt(0) == '0') + throw error("Syntax error: integer values cannot start with '0'"); + return TypeInteger.constant(Long.parseLong(snum)); + } + + // First letter of an identifier + private boolean isIdStart(char ch) { + return Character.isAlphabetic(ch) || ch == '_'; + } + + // All characters of an identifier, e.g. "_x123" + private boolean isIdLetter(char ch) { + return Character.isLetterOrDigit(ch) || ch == '_'; + } + + private String parseId() { + int start = _position; + while (isIdLetter(nextChar())) ; + return new String(_input, start, --_position - start); + } + + // + private boolean isPunctuation(char ch) { + return "=;[]<>()+-/*".indexOf(ch) != -1; + } + + private String parsePunctuation() { + int start = _position; + return new String(_input, start, 1); + } + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/Utils.java b/chapter11/src/main/java/com/seaofnodes/simple/Utils.java new file mode 100644 index 00000000..e3f29b3d --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/Utils.java @@ -0,0 +1,58 @@ +package com.seaofnodes.simple; + +import java.util.ArrayList; + +public class Utils { + public static RuntimeException TODO() { return TODO("Not yet implemented"); } + public static RuntimeException TODO(String msg) { return new RuntimeException(msg); } + + /** + * Fast, constant-time, element removal. Does not preserve order + * + * @param array ArrayList to modify + * @param i element to be removed + * @return element removed + */ + public static <E> E del(ArrayList<E> array, int i) { + if ( i >= 0 && i < array.size() ) { + E tmp = array.get(i); + E last = array.removeLast(); + if (i < array.size()) array.set(i, last); + return tmp; + } + return null; + } + + /** + * Search a list for an element by reference + * + * @param ary List to search in + * @param x Object to be searched + * @return >= 0 on success, -1 on failure + */ + public static <E> int find( ArrayList<E> ary, E x ) { + for( int i=0; i<ary.size(); i++ ) + if( ary.get(i)==x ) + return i; + return -1; + } + /** + * Search a list for an element by reference + * + * @param ary List to search in + * @param x Object to be searched + * @return >= 0 on success, -1 on failure + */ + public static <E> int find( E[] ary, E x ) { + for( int i=0; i<ary.length; i++ ) + if( ary[i]==x ) + return i; + return -1; + } + + // Rotate a long, nice for hashes + public static long rot( long x, int n ) { return (x<<n) | (x>>>n); } + + // Fold a 64bit hash into 32 bits + public static int fold( long x ) { return (int)((x>>32) ^ x); } +}
\ No newline at end of file diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java new file mode 100644 index 00000000..2cc0b465 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -0,0 +1,174 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +public class AddNode extends Node { + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Add"; } + + @Override public String glabel() { return "+"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("+"), visited); + return sb.append(")"); + } + + + @Override + public Type compute() { + if( in(1)._type instanceof TypeInteger i0 && + in(2)._type instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()+i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize () { + Node lhs = in(1); + Node rhs = in(2); + Type t2 = rhs._type; + + // Add of 0. We do not check for (0+x) because this will already + // canonicalize to (x+0) + if( t2 instanceof TypeInteger i && i.value()==0 ) + return lhs; + + // Add of same to a multiply by 2 + if( lhs==rhs ) + return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); + + // Goal: a left-spine set of adds, with constants on the rhs (which then fold). + + // Move non-adds to RHS + if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) + return swap12(); + + // x+(-y) becomes x-y + if( rhs instanceof MinusNode minus ) + return new SubNode(lhs,minus.in(1)); + + // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) + + // Do we have x + (y + z) ? + // Swap to (x + y) + z + // Rotate (add add add) to remove the add on RHS + if( rhs instanceof AddNode add ) + return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + + // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) + if( !(lhs instanceof AddNode) ) + // Rotate; look for (add (phi cons) con/(phi cons)) + return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); + + // Now we only see (add add non) + + // Dead data cycle; comes about from dead infinite loops. Do nothing, + // the loop will peep as dead after a bit. + if( lhs.in(1) == lhs ) + return null; + + // Do we have (x + con1) + con2? + // Replace with (x + (con1+con2) which then fold the constants + // lhs.in(2) is con1 here + // If lhs.in(2) is not a constant, we add ourselves as a dependency + // because if it later became a constant then we could make this + // transformation. + if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.isConstant() ) + return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + + + // Do we have ((x + (phi cons)) + con) ? + // Do we have ((x + (phi cons)) + (phi cons)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + Node phicon = phiCon(this,true); + if( phicon!=null ) return phicon; + + // Now we sort along the spine via rotates, to gather similar things together. + + // Do we rotate (x + y) + z + // into (x + z) + y ? + if( spine_cmp(lhs.in(2),rhs,this) ) + return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); + + return null; + } + + // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or. + // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + static Node phiCon(Node op, boolean rotate) { + Node lhs = op.in(1); + Node rhs = op.in(2); + // LHS is either a Phi of constants, or another op with Phi of constants + PhiNode lphi = pcon(lhs,op); + if( rotate && lphi==null && lhs.nIns() > 2 ) { + // Only valid to rotate constants if both are same associative ops + if( lhs.getClass() != op.getClass() ) return null; + lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push + } + if( lphi==null ) return null; + + // RHS is a constant or a Phi of constants + if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) + return null; + + // If both are Phis, must be same Region + if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) + return null; + + // Note that this is the exact reverse of Phi pulling a common op down + // to reduce total op-count. We don't get in an endless push-up + // push-down peephole cycle because the constants all fold first. + Node[] ns = new Node[lphi.nIns()]; + ns[0] = lphi.in(0); + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + for( int i=1; i<ns.length; i++ ) + ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole(); + String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : ""); + Node phi = new PhiNode(label,lphi._declaredType,ns).peephole(); + // Rotate needs another op, otherwise just the phi + return lhs==lphi ? phi : op.copy(lhs.in(1),phi); + } + + /** + * 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-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 spine_cmp( Node hi, Node lo, Node dep ) { + if( lo._type.isConstant() ) return false; + if( hi._type.isConstant() ) 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; + + // Same category of "others" + return lo._nid > hi._nid; + } + + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java new file mode 100644 index 00000000..9c15f01b --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -0,0 +1,75 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.BitSet; + +abstract public class BoolNode extends Node { + + public BoolNode(Node lhs, Node rhs) { + super(null, lhs, rhs); + } + + abstract String op(); // String opcode name + + @Override + public String label() { return getClass().getSimpleName(); } + + @Override + public String glabel() { return op(); } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append(op()), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + if( in(1)._type instanceof TypeInteger i0 && + in(2)._type instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); + } + return in(1)._type.meet(in(2)._type); + } + + abstract boolean doOp(long lhs, long rhs); + + @Override + public Node idealize() { + // Compare of same + if( in(1)==in(2) ) + return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0)); + + // Equals pushes constant to the right; 5==X becomes X==5. + if( this instanceof EQ ) { + if( !(in(2) instanceof ConstantNode) ) { + // con==noncon becomes noncon==con + if( in(1) instanceof ConstantNode ) + return new EQ(in(2),in(1)); + // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high + else if( in(1)._nid > in(2)._nid ) + return new EQ(in(2),in(1)); + } + // Equals X==0 becomes a !X + if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) + return new NotNode(in(1)); + } + + // Do we have ((x * (phi cons)) * con) ? + // Do we have ((x * (phi cons)) * (phi cons)) ? + // Push constant up through the phi: x * (phi con0*con0 con1*con1...) + Node phicon = AddNode.phiCon(this,this instanceof EQ); + if( phicon!=null ) return phicon; + + return null; + } + + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } } + public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } } + public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java new file mode 100644 index 00000000..b605071b --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -0,0 +1,92 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.Utils; + +import java.util.BitSet; +import java.util.HashSet; +import java.util.HashMap; + +/** Control Flow Graph Nodes + * <p> + * CFG nodes have a immediate dominator depth (idepth) and a loop nesting + * depth(loop_depth). + * <p> + * idepth is computed lazily upon first request, and is valid even in the + * Parser, and is used by peepholes during parsing and afterward. + * <p> + * loop_depth is computed after optimization as part of scheduling. + * + */ +public abstract class CFGNode extends Node { + + public CFGNode(Node... nodes) { super(nodes); } + + @Override public boolean isCFG() { return true; } + @Override public boolean isPinned() { return true; } + + public CFGNode cfg(int idx) { return (CFGNode)in(idx); } + + // Block head is Start, Region, CProj, but not e.g. If, Return, Stop + public boolean blockHead() { return false; } + + // Should be exactly 1 tail from a block head + public CFGNode blockTail() { + assert blockHead(); + for( Node n : _outputs ) + if( n instanceof CFGNode cfg ) + return cfg; + return null; + } + + // ------------------------------------------------------------------------ + /** + * Immediate dominator tree depth, used to approximate a real IDOM during + * parsing where we do not have the whole program, and also peepholes + * change the CFG incrementally. + * <p> + * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>} + */ + public int _idepth; + public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; } + + // Return the immediate dominator of this Node and compute dom tree depth. + public CFGNode idom() { return cfg(0); } + + // Return the LCA of two idoms + public CFGNode idom(CFGNode rhs) { + if( rhs==null ) return this; + CFGNode lhs = this; + while( lhs != rhs ) { + var comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } + + // Loop nesting depth + public int _loopDepth; + public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(0).loopDepth()) : _loopDepth; } + + // Anti-dependence field support + public int _anti; // Per-CFG field to help find anti-deps + + // ------------------------------------------------------------------------ + // Support routines for Global Code Motion + + // Tik-tok recursion pattern. This method is final, and every caller does + // this work. + public final void walkUnreach( BitSet visit, HashSet<CFGNode> unreach ) { + if( visit.get(_nid) ) return; + visit.set(_nid); + _walkUnreach(visit,unreach); + unreach.remove(this); // Since we reached here... Node was not unreachable + } + + // Tik-tok recursion pattern; not-final; callers override this. + void _walkUnreach( BitSet visit, HashSet<CFGNode> unreach ) { + cfg(0).walkUnreach(visit,unreach); + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java new file mode 100644 index 00000000..3d6fd1fa --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; +import java.util.BitSet; + +public class CProjNode extends CFGNode { + + // Which slice of the incoming multipart value + public final int _idx; + + // Debugging label + public final String _label; + + public CProjNode(Node ctrl, int idx, String label) { + super(ctrl); + _idx = idx; + _label = label; + } + + @Override public String label() { return _label; } + + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } + + @Override public boolean isMultiTail() { return in(0).isMultiHead(); } + @Override public boolean blockHead() { return true; } + + public CFGNode ctrl() { return cfg(0); } + + @Override + public Type compute() { + Type t = ctrl()._type; + return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; + } + + @Override + public Node idealize() { + if( ctrl()._type instanceof TypeTuple tt ) { + if( tt._types[_idx]==Type.XCONTROL ) + return Parser.XCTRL; // We are dead + if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes + return ctrl().in(0); // We become our input control + } + + // Flip a negating if-test, to remove the not + if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not ) + return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); + + return null; + } + + @Override + public Node getBlockStart() { + return ctrl() instanceof IfNode ? this : ctrl().getBlockStart(); + } + @Override + boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } + + @Override + int hash() { return _idx; } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/CastNode.java new file mode 100644 index 00000000..fec540f7 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.IterPeeps; + +import java.util.BitSet; + +// Upcast (join) the input to a t. Used after guard test to lift an input. +// Can also be used to make a type-assertion if ctrl is null. +public class CastNode extends Node { + private final Type _t; + public CastNode(Type t, Node ctrl, Node in) { + super(ctrl, in); + _t = t; + setType(compute()); + } + + @Override public String label() { return "("+_t.str()+")"; } + + @Override + public String uniqueName() { return "Cast_" + _nid; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append(label()), visited); + } + + @Override + public Type compute() { + return in(1)._type.join(_t); + } + + @Override + public Node idealize() { + return in(1)._type.isa(_t) ? in(1) : null; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java new file mode 100644 index 00000000..cf1a76b0 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; + +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 + * Constants. + * <p> + * Constants have no semantic inputs. However, we set Start as an input to + * Constants to enable a forward graph walk. This edge carries no semantic + * meaning, and it is present <em>solely</em> to allow visitation. + * <p> + * The Constant's value is the value stored in it. + */ +public class ConstantNode extends Node { + Type _con; + public ConstantNode( Type type ) { + super(Parser.START); + _con = type; + } + + @Override + public String label() { return "#"+_con; } + + @Override + public String uniqueName() { return "Con_" + _nid; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return _con._print(sb); + } + + @Override public boolean isMultiTail() { return true; } + // The special forever-zero is pinned, all others can sink + @Override public boolean isPinned() { return this==Parser.ZERO; } + + @Override + public Type compute() { return _con; } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { + ConstantNode con = (ConstantNode)n; // Contract + return _con==con._con; + } + + @Override + int hash() { return _con.hashCode(); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java new file mode 100644 index 00000000..ed1a233c --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.BitSet; + +public class DivNode extends Node { + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override public String glabel() { return "//"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("/"), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeInteger i0 && + in(2)._type instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return i1.value() == 0 + ? TypeInteger.ZERO + : TypeInteger.constant(i0.value()/i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize() { return null; } + + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java new file mode 100644 index 00000000..5457ac6e --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -0,0 +1,71 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeTuple; +import java.util.BitSet; +import java.util.HashSet; + +public class IfNode extends CFGNode implements MultiNode { + + public IfNode(Node ctrl, Node pred) { + super(ctrl, pred); + IterPeeps.add(this); // Because idoms are complex, just add it + } + + @Override + public String label() { return "If"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("if( "); + return in(1)._print0(sb, visited).append(" )"); + } + + @Override public boolean isMultiHead() { return true; } + + public Node ctrl() { return in(0); } + public Node pred() { return in(1); } + + @Override + public Type compute() { + // If the If node is not reachable then neither is any following Proj + if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) + return TypeTuple.IF_NEITHER; + Node pred = pred(); + Type t = pred._type; + // High types mean NEITHER side is reachable. + // Wait until the type falls to decide which way to go. + if( t == Type.TOP || t == TypeInteger.TOP ) + return TypeTuple.IF_NEITHER; + // If constant is 0 then false branch is reachable + // Else true branch is reachable + if (t instanceof TypeInteger ti && ti.isConstant()) + return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + + return TypeTuple.IF_BOTH; + } + + @Override + public Node idealize() { + // Hunt up the immediate dominator tree. If we find an identical if + // test on either the true or false branch, that side wins. + if( !pred()._type.isHighOrConst() ) + for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { + setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); + return this; + } + return null; + } + + @Override void _walkUnreach( BitSet visit, HashSet<CFGNode> unreach ) { + for( Node proj : _outputs ) + if( ((CProjNode)proj)._loopDepth == 0 ) + unreach.add((CProjNode)proj); + super._walkUnreach(visit,unreach); + } + + @Override public Node getBlockStart() { return ctrl().getBlockStart(); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java new file mode 100644 index 00000000..d728b818 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -0,0 +1,65 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.Field; + +import java.util.BitSet; + +/** + * Load represents extracting a value from inside a memory object, + * in chapter 10 this means Struct fields. + */ +public class LoadNode extends MemOpNode { + + /** + * Load a value from a ptr.field. + * + * @param field The struct field we are loading + * @param memSlice The memory alias node - this is updated after a Store + * @param memPtr The ptr to the struct from where we load a field + */ + public LoadNode(Field field, Node memSlice, Node memPtr) { + super(field, memSlice, memPtr); + } + + @Override + public String label() { return "Load"; } + + @Override + public String glabel() { return "."+_field._fname; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_field._fname); } + + @Override + public Type compute() { + return _field._type; + } + + @Override + public Node idealize() { + + // Simple Load-after-Store on same address. + if( mem() instanceof StoreNode st && + ptr() == st.ptr() ) { // Must check same object + assert _field==st._field; // Equiv class aliasing is perfect + return st.val(); + } + + // Push a Load up through a Phi, as long as it collapses on at least one arm. + // BEFORE (2 Sts, 1 Ld): AFTER (1 St): + // if( pred ) ptr.x = e0; val = pred ? e0 + // else ptr.x = e1; : e1; + // val = ptr.x; ptr.x = val; + if( mem() instanceof PhiNode phi && phi.nIns()== 3 && + ((phi.in(1) instanceof StoreNode st1 && ptr()==st1.ptr() && phi.in(2)!=phi ) || + (phi.in(2) instanceof StoreNode st2 && ptr()==st2.ptr() && phi.in(1)!=phi ) ) ) { + Node ld1 = new LoadNode(_field,phi.in(1),ptr()).peephole(); + Node ld2 = new LoadNode(_field,phi.in(2),ptr()).peephole(); + return new PhiNode(_field._fname,_type,phi.region(),ld1,ld2); + } + + return null; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java new file mode 100644 index 00000000..c662dee6 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -0,0 +1,77 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; +import java.util.HashSet; + +public class LoopNode extends RegionNode { + public LoopNode( Node entry ) { super(null,entry,null); } + + public CFGNode entry() { return cfg(1); } + public CFGNode back () { return cfg(2); } + + @Override + public String label() { return "Loop"; } + + @Override + public Type compute() { + if( inProgress() ) return Type.CONTROL; + return entry()._type; + } + + @Override + public Node idealize() { + return inProgress() ? null : super.idealize(); + } + + // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) + @Override public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; } + // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) + @Override public CFGNode idom() { return entry(); } + + @Override public int loopDepth() { + if( _loopDepth!=0 ) return _loopDepth; // Was already set + _loopDepth = entry()._loopDepth+1; // Entry depth plus one + // One-time tag loop exits + for( CFGNode idom = back(); idom!=this; idom = idom.idom() ) { + // Walk idom in loop, setting depth + idom._loopDepth = _loopDepth; + // Loop exit hits the CProj before the If, instead of jumping from + // Region directly to If. + if( idom instanceof CProjNode proj ) { + assert proj.in(0) instanceof IfNode; // Loop exit test + // Find the loop exit CProj, and set loop_depth + for( Node use : proj.in(0)._outputs ) + if( use instanceof CProjNode proj2 && proj2 != idom ) + proj2._loopDepth = _loopDepth-1; + } + } + return _loopDepth; + } + + // If this is an unreachable loop, it may not have an exit. If it does not + // (i.e., infinite loop), force an exit to make it reachable. + public void forceExit( StopNode stop ) { + // Walk the backedge, then immediate dominator tree util we hit this + // Loop again. If we ever hit a CProj from an If (as opposed to + // directly on the If) we found our exit. + CFGNode x = back(); + while( x != this ) { + if( x instanceof CProjNode exit ) + return; // Found an exit, not an infinite loop + x = x.idom(); + } + // Found a no-exit loop. Insert an exit + NeverNode iff = new NeverNode(back()); + for( Node use : _outputs ) + if( use instanceof PhiNode phi ) + iff.addDef(use); + CProjNode t = new CProjNode(iff,0,"True" ); + CProjNode f = new CProjNode(iff,1,"False"); + setDef(2,f); + stop.addDef(new ReturnNode(t,Parser.ZERO,null)); + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java new file mode 100644 index 00000000..a8fb18ec --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +/** + * Convenience common base for Load and Store. + */ +abstract class MemOpNode extends Node { + + protected Field _field; + + public MemOpNode(Field field, Node memSlice, Node memPtr) { + super(null, memSlice, memPtr); + _field = field; + } + public MemOpNode(Field field, Node memSlice, Node memPtr, Node value) { + this(field, memSlice, memPtr); + addDef(value); + } + + public Node mem() { return in(1); } + public Node ptr() { return in(2); } + + @Override + boolean eq(Node n) { + MemOpNode mem = (MemOpNode)n; // Invariant + return _field.equals(mem._field); + } + + @Override + int hash() { + return _field._alias; + } + + @Override + String err() { + Type ptr = ptr()._type; + return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) ) + ? "Might be null accessing '" + _field.str() + "'" + : null; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java new file mode 100644 index 00000000..524f63a0 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.BitSet; + +public class MinusNode extends Node { + public MinusNode(Node in) { super(null, in); } + + @Override public String label() { return "Minus"; } + + @Override public String glabel() { return "-"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("(-"), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeInteger i0) + return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; + return TypeInteger.TOP.meet(in(1)._type); + } + + @Override + public Node idealize() { return null; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java new file mode 100644 index 00000000..d63d2cb1 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -0,0 +1,57 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.BitSet; + +public class MulNode extends Node { + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Mul"; } + + @Override public String glabel() { return "*"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("*"), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeInteger i0 && + in(2)._type instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()*i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Mul of 1. We do not check for (1*x) because this will already + // canonicalize to (x*1) + if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x * (phi cons)) * con) ? + // Do we have ((x * (phi cons)) * (phi cons)) ? + // Push constant up through the phi: x * (phi con0*con0 con1*con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java new file mode 100644 index 00000000..379e140d --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -0,0 +1,12 @@ +package com.seaofnodes.simple.node; + +public interface MultiNode extends OutNode { + + // Find a projection by index + default ProjNode proj( int idx ) { + for( Node out : outs() ) + if( out instanceof ProjNode prj && prj._idx==idx ) + return prj; + return null; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java new file mode 100644 index 00000000..f50d3503 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.Type; +import java.util.BitSet; + +// "Never true" for infinite loop exits +public class NeverNode extends IfNode { + public NeverNode(Node ctrl) { super(ctrl,Parser.ZERO); } + + @Override public String label() { return "Never"; } + + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); } + + @Override public Type compute() { return TypeInteger.BOT; } + + @Override public Node idealize() { return null; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java new file mode 100644 index 00000000..0724ec56 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.BitSet; + +public class NewNode extends Node { + + TypeMemPtr _ptr; + + public NewNode(TypeMemPtr ptr, Node ctrl) { + super(ctrl); + this._ptr = ptr; + } + + @Override + public String label() { return "new"; } + + @Override + public String glabel() { return "new " + _ptr._obj._name; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("new "); + return sb.append(_ptr._obj.str()); + } + + @Override public boolean isPinned() { return true; } + + @Override + public Type compute() { return _ptr; } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { return this == n; } + + @Override + int hash() { return _ptr.hashCode(); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java new file mode 100644 index 00000000..3532dbdd --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java @@ -0,0 +1,621 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IRPrinter; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; + +import java.util.*; +import java.util.function.Function; + +/** + * All Nodes in the Sea of Nodes IR inherit from the Node class. + * The Node class provides common functionality used by all subtypes. + * Subtypes of Node specialize by overriding methods. + */ +public abstract class Node implements OutNode { + + /** + * Each node has a unique dense Node ID within a compilation context + * The ID is useful for debugging, for using as an offset in a bitvector, + * as well as for computing equality of nodes (to be implemented later). + */ + public final int _nid; + + /** + * Inputs to the node. These are use-def references to Nodes. + * <p> + * Generally fixed length, ordered, nulls allowed, no unused trailing space. + * Ordering is required because e.g. "a/b" is different from "b/a". + * The first input (offset 0) is often a {@link #isCFG} node. + */ + public final ArrayList<Node> _inputs; + + /** + * Outputs reference Nodes that are not null and have this Node as an + * input. These nodes are users of this node, thus these are def-use + * references to Nodes. + * <p> + * Outputs directly match inputs, making a directed graph that can be + * walked in either direction. These outputs are typically used for + * efficient optimizations but otherwise have no semantics meaning. + */ + public final ArrayList<Node> _outputs; + + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public Type _type; + + + /** + * A private Global Static mutable counter, for unique node id generation. + * To make the compiler multithreaded, this field will have to move into a TLS. + * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values. + * */ + private static int UNIQUE_ID = 1; + public static int UID() { return UNIQUE_ID; } + + protected Node(Node... inputs) { + _nid = UNIQUE_ID++; // allocate unique dense ID + _inputs = new ArrayList<>(); + Collections.addAll(_inputs,inputs); + _outputs = new ArrayList<>(); + for( Node n : _inputs ) + if( n != null ) + n.addUse( this ); + } + + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" + public abstract String label(); + + // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + public String uniqueName() { + // Get rid of $ as graphviz doesn't like it + String label = label().replaceAll("\\$", ""); + return label + _nid; + } + + // Graphical label, e.g. "+" or "Region" or "==" + public String glabel() { return label(); } + + + // ------------------------------------------------------------------------ + + // {@code toString} is what you get in the debugger. It has to print 1 + // line (because this is what a debugger typically displays by default) and + // has to be robust with broken graph/nodes. + @Override + public final String toString() { return print(); } + + // This is a *deep* print. This version will fail on cycles, which we will + // correct later when we can parse programs with loops. We print with a + // tik-tok style; the common _print0 calls the per-Node _print1, which + // calls back to _print0; + public final String print() { + return _print0(new StringBuilder(), new BitSet()).toString(); + } + + // This is the common print: check for repeats, check for DEAD and print + // "DEAD" else call the per-Node print1. + final StringBuilder _print0(StringBuilder sb, BitSet visited) { + if (visited.get(_nid) && !(this instanceof ConstantNode) ) + return sb.append(label()); + visited.set(_nid); + return isDead() + ? sb.append(uniqueName()).append(":DEAD") + : _print1(sb, visited); + } + // Every Node implements this; a partial-line recursive print + abstract StringBuilder _print1(StringBuilder sb, BitSet visited); + + public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } + + public boolean isMultiHead() { return false; } + public boolean isMultiTail() { return false; } + + // ------------------------------------------------------------------------ + // Graph Node & Edge manipulation + + /** + * Gets the ith input node + * @param i Offset of the input node + * @return Input node or null + */ + public Node in(int i) { return _inputs.get(i); } + public Node out(int i) { return _outputs.get(i); } + @Override public ArrayList<Node> outs() { return _outputs; } + + public int nIns() { return _inputs.size(); } + + public int nOuts() { return _outputs.size(); } + + public boolean isUnused() { return nOuts() == 0; } + + public CFGNode cfg0() { return (CFGNode)in(0); } + + /** + * Change a <em>def</em> into a Node. Keeps the edges correct, by removing + * the corresponding <em>use->def</em> edge. This may make the original + * <em>def</em> go dead. This function is co-recursive with {@link #kill}. + * <p> + + * This method is the normal path for altering a Node, because it does the + * proper default edge maintenance. It also <em>immediately</em> kills + * Nodes that lose their last use; at times care must be taken to avoid + * killing Nodes that are being used without having an output Node. This + * definitely happens in the middle of recursive {@link #peephole} calls. + * + * @param idx which def to set + * @param new_def the new definition + * @return new_def for flow coding + */ + public Node setDef(int idx, Node new_def ) { + unlock(); + Node old_def = in(idx); + if( old_def == new_def ) return new_def; // No change + // If new def is not null, add the corresponding def->use edge + // This needs to happen before removing the old node's def->use edge as + // the new_def might get killed if the old node kills it recursively. + if( new_def != null ) + new_def.addUse(this); + // Set the new_def over the old (killed) edge + _inputs.set(idx,new_def); + if( old_def != null && // If the old def exists, remove a def->use edge + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + moveDepsToWorklist(); + // Return self for easy flow-coding + return new_def; + } + + // Remove the numbered input, compressing the inputs in-place. This + // shuffles the order deterministically - which is suitable for Region and + // Phi, but not for every Node. If the def goes dead, it is recursively + // killed, which may include 'this' Node. + Node delDef(int idx) { + unlock(); + Node old_def = in(idx); + if( old_def != null && // If the old def exists, remove a def->use edge + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + old_def.moveDepsToWorklist(); + Utils.del(_inputs, idx); + return this; + } + + /** + * Add a new def to an existing Node. Keep the edges correct by + * adding the corresponding <em>def->use</em> edge. + * + * @param new_def the new definition, appended to the end of existing definitions + * @return new_def for flow coding + */ + public Node addDef(Node new_def) { + unlock(); + // Add use->def edge + _inputs.add(new_def); + // If new def is not null, add the corresponding def->use edge + if( new_def != null ) + new_def.addUse(this); + return new_def; + } + + // Breaks the edge invariants, used temporarily + protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } + + // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. + // Return true if the output list is empty afterward. + // Error is 'use' does not exist; ok for 'use' to be null. + protected boolean delUse( Node use ) { + Utils.del(_outputs, Utils.find(_outputs, use)); + return _outputs.isEmpty(); + } + + // Shortcut for "popping" n nodes. A "pop" is basically a + // setDef(last,null) followed by lowering the nIns() count. + void popN(int n) { + unlock(); + for( int i=0; i<n; i++ ) { + Node old_def = _inputs.removeLast(); + if( old_def != null && // If it exists and + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + } + } + + /** + * Kill a Node with no <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. + */ + public void kill( ) { + unlock(); + assert isUnused(); // Has no uses, so it is dead + _type=null; // Flag as dead + while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes + Node old_def = _inputs.removeLast(); + if( old_def != null ) { + IterPeeps.add(old_def);// Revisit neighbor because removed use + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + } + } + assert isDead(); // Really dead now + } + + // Mostly used for asserts and printing. + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + + // Shortcuts to stop DCE mid-parse + // Add bogus null use to keep node alive + public <N extends Node> N keep() { return addUse(null); } + // Remove bogus null. + public <N extends Node> N unkeep() { + assert Utils.find(_outputs, null) != -1; + delUse(null); + return (N)this; + } + + + // Replace self with nnn in the graph, making 'this' go dead + public void subsume( Node nnn ) { + assert nnn!=this; + while( nOuts() > 0 ) { + Node n = _outputs.removeLast(); + n.unlock(); + int idx = Utils.find(n._inputs, this); + n._inputs.set(idx,nnn); + nnn.addUse(n); + } + kill(); + } + + // ------------------------------------------------------------------------ + // Graph-based optimizations + + /** + * We allow disabling peephole opt so that we can observe the + * full graph, vs the optimized graph. + */ + 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> + * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li> + * <li>in a future chapter we will look for a + * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a> + * to eliminate.</li> + * <li>we ask the Node for a better replacement. The "better replacement" + * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes + * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing + * math constants, remove algebraic identities and generally simplify the + * code. </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 peepholeOpt( ) { + ITER_CNT++; + // Compute initial or improved Type + Type old = setType(compute()); + + // Replace constant computations from non-constants with a constant node + if( !(this instanceof ConstantNode) && !(this instanceof XCtrlNode) && _type.isHighOrConst() ) + return (_type==Type.XCONTROL ? new XCtrlNode() : new ConstantNode(_type)).peepholeOpt(); + + // Global Value Numbering + if( _hash==0 ) { + Node n = GVN.get(this); // Will set _hash as a side effect + if( n==null ) + GVN.put(this,this); // Put in table now + else { + // Because of random worklist ordering, the two equal nodes + // might have different types. Because of monotonicity, both + // types are valid. To preserve monotonicity, the resulting + // shared Node has to have the best of both types. + n.setType(n._type.join(_type)); + _hash = 0; // Clear, since it never went in the table + return deadCodeElim(n);// Return previous; does Common Subexpression Elimination + } + } + + // Ask each node for a better replacement + Node n = idealize(); + if( n != null ) // Something changed + return n; // Report progress + + if( old==_type ) ITER_NOP_CNT++; + return old==_type ? null : this; // Report progress + } + public static int ITER_CNT, ITER_NOP_CNT; + + // m is the new Node, self is the old. + // Return 'm', which may have zero uses but is alive nonetheless. + // If self has zero uses (and is not 'm'), {@link #kill} self. + private Node deadCodeElim(Node m) { + // If self is going dead and not being returned here (Nodes returned + // from peephole commonly have no uses (yet)), then kill self. + if( m != this && isUnused() ) { + // Killing self - and since self recursively kills self's inputs we + // might end up killing 'm', which we are returning as a live Node. + // So we add a bogus extra null output edge to stop kill(). + m.keep(); // Keep m alive + kill(); // Kill self because replacing with 'm' + m.unkeep(); // Okay to peephole m + } + return m; + } + + /** + * This function needs to be + * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a> + * as it is part of a Monotone Analysis Framework. + * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>. + * <p> + * For Chapter 2, all our Types are really integer constants, and so all + * the needed properties are trivially true, and we can ignore the high + * theory. Much later on, this will become important and allow us to do + * many fancy complex optimizations trivially... because theory. + * <p> + * {@link #compute()} needs to be stand-alone, and cannot recursively call compute + * on its inputs, because programs are cyclic (have loops!) and this will just + * infinitely recurse until stack overflow. Instead, compute typically + * computes a new type from the {@link #_type} field of its inputs. + */ + public abstract Type compute(); + + // Set the type. Assert monotonic progress. + // If changing, add users to worklist. + public Type setType(Type type) { + Type old = _type; + assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger + if( old == type ) return old; + _type = type; // Set _type late for easier assert debugging + IterPeeps.addAll(_outputs); + moveDepsToWorklist(); + return old; + } + + + /** + * This function rewrites the current Node into a more "idealized" form. + * This is the bulk of our peephole rewrite rules, and we use this to + * e.g. turn arbitrary collections of adds and multiplies with mixed + * constants into a normal form that's easy for hardware to implement. + * Example: An array addressing expression: + * <pre> ary[idx+1]</pre> + * might turn into Sea-of-Nodes IR: + * <pre> (ary+12)+((idx+1) * 4)</pre> + * This expression can then be idealized into: + * <pre> ary + ((idx*4) + (12 + (1*4)))</pre> + * And more folding: + * <pre> ary + ((idx<<2) + 16)</pre> + * And during code-gen: + * <pre> MOV4 Rary,Ridx,16 // or some such hardware-specific notation </pre> + * <p> + * {@link #idealize} has a very specific calling convention: + * <ul> + * <li>If NO change is made, return {@code null} + * <li>If ANY change is made, return not-null; this can be {@code this} + * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. + * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}. + * </ul> + * <p> + * Examples: + * <table border="3"> + * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr> + * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr> + * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr> + * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr> + * </table> + * + * The last entry deserves more discussion. The new Node {@code (1+2)} + * created in {@link #idealize} calls {@link #peephole} (which then folds + * into a constant). The other new Node {@code (x+3)} does not call + * peephole, because it is returned and peephole itself will recursively + * call peephole. + * <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! + * + * @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 of dependencies. Only add it if its not an input + * or output of this node, that is, it is at least one step away. The node + * being added must benefit from this node being peepholed. + */ + Node addDep( Node dep ) { + // 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; } + + // ------------------------------------------------------------------------ + // + + /** Is this Node control-flow-graph related */ + public boolean isCFG() { return false; } + + /** Is this Node Memory related */ + public boolean isMem() { return false; } + + /** Return block start from a isCFG() */ + public Node getBlockStart() { return null; } + + /** Pinned in the schedule */ + public boolean isPinned() { return false; } + + // ------------------------------------------------------------------------ + // Peephole utilities + + // Swap inputs without letting either input go dead during the swap. + Node swap12() { + unlock(); // Hash is order dependent + Node tmp = in(1); + _inputs.set(1,in(2)); + _inputs.set(2,tmp); + return this; + } + + /** + * Does this node contain all constants? Ignores in(0), as is usually + * control. In an input is not a constant, we add dep as a dependency to + * it because dep can make progress if the input becomes a constant later. + * It is sufficient for one of the non-const inputs to have the dependency, + * so we don't bother checking the rest. + */ + boolean allCons(Node dep) { + for( int i=1; i<nIns(); i++ ) + if( !(in(i)._type.isConstant()) ) { + in(i).addDep(dep); // If in(i) becomes a constant later, will trigger some peephole + return false; + } + return true; + } + + + // Make a shallow copy (same class) of this Node, with given inputs and + // empty outputs and a new Node ID. The original inputs are ignored. + // Does not need to be implemented in isCFG() nodes. + Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + + // Report any post-optimize errors + String err() { return null; } + + /** + * Used to allow repeating tests in the same JVM. This just resets the + * Node unique id generator, and is done as part of making a new Parser. + */ + public static void reset() { + UNIQUE_ID = 1; + _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 walk( n -> n._nid==nid ? n : null ); + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java new file mode 100644 index 00000000..05e44eb0 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.*; +import java.util.BitSet; + +public class NotNode extends Node { + public NotNode(Node in) { super(null, in); } + + @Override public String label() { return "Not"; } + + @Override public String glabel() { return "!"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("(!"), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + Type t0 = in(1)._type; + switch( t0 ) { + case TypeInteger i0: + return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0; + case TypeMemPtr p0: + // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot + // If input in null then true + // If input is not null ptr then false + if( p0 == TypeMemPtr.TOP ) return TypeInteger.TOP; + if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.constant(1); + if( !p0._nil ) return TypeInteger.constant(0); + return TypeInteger.BOT; + case Type t: + if( t0.getClass() != Type.class ) + // Only doing NOT on ints and ptrs + throw Utils.TODO(); + return t0==Type.TOP ? Type.TOP : Type.BOTTOM; + } + } + + @Override + public Node idealize() { return null; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java new file mode 100644 index 00000000..7e531145 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/OutNode.java @@ -0,0 +1,7 @@ +package com.seaofnodes.simple.node; + +import java.util.ArrayList; + +public interface OutNode { + ArrayList<Node> outs(); +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java new file mode 100644 index 00000000..46cc3369 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -0,0 +1,159 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.Utils; + +import java.util.BitSet; + +public class PhiNode extends Node { + + final String _label; + + // The Phi type we compute must stay within the domain of the Phi. + // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. + final Type _declaredType; + + public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } + + @Override public String label() { return "Phi_"+_label; } + + @Override public String glabel() { return "φ_"+_label; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( !(region() instanceof RegionNode r) || r.inProgress() ) + sb.append("Z"); + sb.append("Phi("); + for( Node in : _inputs ) { + if (in == null) sb.append("____"); + else in._print0(sb, visited); + sb.append(","); + } + sb.setLength(sb.length()-1); + sb.append(")"); + return sb; + } + + public CFGNode region() { return (CFGNode)in(0); } + @Override public boolean isMultiTail() { return true; } + @Override public boolean isMem() { return _declaredType instanceof TypeMem; } + @Override public boolean isPinned() { return true; } + + @Override + public Type compute() { + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? Type.TOP : _type; + // During parsing Phis have to be computed type pessimistically. + if( r.inProgress() ) return _declaredType; + // Set type to local top of the starting type + Type t = _declaredType.glb().dual(); + for (int i = 1; i < nIns(); i++) + // If the region's control input is live, add this as a dependency + // to the control because we can be peeped should it become dead. + if( r.in(i).addDep(this)._type != Type.XCONTROL ) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || r.nIns()<=1 ) + return null; // Input is in-progress + + // If we have only a single unique input, become it. + Node live = singleUniqueInput(); + if (live != null) + return live; + + // Pull "down" a common data op. One less op in the world. One more + // Phi, but Phis do not make code. + // Phi(op(A,B),op(Q,R),op(X,Y)) becomes + // op(Phi(A,Q,X), Phi(B,R,Y)). + Node op = in(1); + if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { + Node[] lhss = new Node[nIns()]; + Node[] rhss = new Node[nIns()]; + lhss[0] = rhss[0] = in(0); // Set Region + for( int i=1; i<nIns(); i++ ) { + lhss[i] = in(i).in(1); + rhss[i] = in(i).in(2); + } + Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole(); + Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole(); + return op.copy(phi_lhs,phi_rhs); + } + + // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. + if( nIns()==3 ) { + if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2); + if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1); + } + // If merging a null-checked null and the checked value, just use the value. + // if( val ) ..; phi(Region,False=0/null,True=val); + // then replace with plain val. + if( nIns()==3 ) { + int nullx = -1; + if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; + if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; + if( nullx != -1 ) { + Node val = in(3-nullx); + if( region().idom() instanceof IfNode iff && iff.pred()==val ) { + // Must walk the idom on the null side to make sure we hit False. + CFGNode idom = (CFGNode)region().in(nullx); + while( idom.in(0) != iff ) idom = idom.idom(); + if( idom instanceof CProjNode proj && proj._idx==1 ) + return val; + } + } + } + + return null; + } + + private boolean same_op() { + for( int i=2; i<nIns(); i++ ) + if( in(1).getClass() != in(i).getClass() ) + return false; + return true; + } + + /** + * 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 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; + } + return live; + } + + @Override + 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/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java new file mode 100644 index 00000000..f94e98a7 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -0,0 +1,46 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; + +import java.util.BitSet; + +public class ProjNode extends Node { + + // Which slice of the incoming multipart value + public final int _idx; + + // Debugging label + public final String _label; + + public ProjNode(Node ctrl, int idx, String label) { + super(ctrl); + _idx = idx; + _label = label; + } + + @Override + public String label() { return _label; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } + + @Override public boolean isMultiTail() { return in(0).isMultiHead(); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public boolean isPinned() { return true; } + + @Override + public Type compute() { + Type t = in(0)._type; + return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; + } + + @Override public Node idealize() { return null; } + + @Override + boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } + + @Override + int hash() { return _idx; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java new file mode 100644 index 00000000..0b7eeb46 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -0,0 +1,119 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashSet; + +public class RegionNode extends CFGNode { + + public RegionNode(Node... nodes) { super(nodes); } + + @Override + public String label() { return "Region"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()).append(_nid); + } + + @Override public boolean isMultiHead() { return true; } + @Override public boolean blockHead() { return true; } + + @Override + public Type compute() { + if( inProgress() ) return Type.CONTROL; + Type t = Type.XCONTROL; + for (int i = 1; i < nIns(); i++) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( inProgress() ) return null; + int path = findDeadInput(); + if( path != 0 && + // Do not delete the entry path of a loop (ok to remove the back + // edge and make the loop a single-entry Region which folds away + // the Loop). Folding the entry path confused the loop structure, + // moving the backedge to the entry point. + !(this instanceof LoopNode loop && loop.entry()==in(path)) ) { + // Cannot use the obvious output iterator here, because a Phi + // deleting an input might recursively delete *itself*. This + // shuffles the output array, and we might miss iterating an + // unrelated Phi. So on rare occasions we repeat the loop to get + // all the Phis. + int nouts = 0; + while( nouts != nOuts() ) { + nouts = nOuts(); + for( int i=0; i<nOuts(); i++ ) + if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) { + phi.delDef(path); + IterPeeps.addAll(phi._outputs); + } + } + return isDead() ? Parser.XCTRL : delDef(path); + } + // If down to a single input, become that input + if( nIns()==2 && !hasPhi() ) + return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own + return null; + } + + private int findDeadInput() { + for( int i=1; i<nIns(); i++ ) + if( in(i)._type==Type.XCONTROL ) + return i; + 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. + @Override public int idepth() { + if( _idepth!=0 ) return _idepth; + int d=0; + for( Node n : _inputs ) + if( n!=null ) + d = Math.max(d,((CFGNode)n).idepth()+1); + return _idepth=d; + } + + @Override public CFGNode idom() { + CFGNode lca = null; + // Walk the LHS & RHS idom trees in parallel until they match, or either fails. + // Because this does not cache, it can be linear in the size of the program. + for( int i=1; i<nIns(); i++ ) + lca = cfg(i).idom(lca); + return lca; + } + + @Override void _walkUnreach( BitSet visit, HashSet<CFGNode> unreach ) { + for( int i=1; i<nIns(); i++ ) + cfg(i).walkUnreach(visit,unreach); + } + + @Override public int loopDepth() { return _loopDepth==0 ? (_loopDepth = cfg(1).loopDepth()) : _loopDepth; } + + // True if last input is null + public final boolean inProgress() { + return nIns()>1 && in(nIns()-1) == null; + } + + // Never equal if inProgress + @Override boolean eq( Node n ) { + return !inProgress(); + } + + @Override public Node getBlockStart() { return this; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java new file mode 100644 index 00000000..444b76a8 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,57 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +/** + * The Return node has two inputs. The first input is a control node and the + * second is the data node that supplies the return value. + * <p> + * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. + * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. + * <p> + * The Return's output is the value from the data node. + */ +public class ReturnNode extends CFGNode { + + public ReturnNode(Node ctrl, Node data, ScopeNode scope) { + super(ctrl, data); + // We lookup memory slices by the naming convention that they start with $ + // We could also use implicit knowledge that all memory projects are at offset >= 2 + if( scope != null ) { + String[] names = scope.reverseNames(); + for (String name: names) { + if (!name.equals("$ctrl") && name.startsWith("$")) + addDef(scope.lookup(name)); + } + } + } + + public Node ctrl() { return in(0); } + public Node expr() { return in(1); } + + @Override + public String label() { return "Return"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("return "); + expr()._print0(sb, visited); + return sb.append(";"); + } + + @Override + public Type compute() { + return TypeTuple.make(ctrl()._type,expr()._type); + } + + @Override + public Node idealize() { + if( ctrl()._type==Type.XCONTROL ) + return ctrl(); + return null; + } + + @Override public Node getBlockStart() { return ctrl().getBlockStart(); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java new file mode 100644 index 00000000..8a50eb56 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -0,0 +1,296 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.*; + +/** + * The Scope node is purely a parser helper - it tracks names to nodes with a + * stack of scopes. + */ +public class ScopeNode extends Node { + + /** + * The control is a name that binds to the currently active control + * node in the graph + */ + public static final String CTRL = "$ctrl"; + public static final String ARG0 = "arg"; + + /** + * Names for every input edge + */ + public final Stack<HashMap<String, Integer>> _scopes; + + /** + * Tracks declared types for every name + */ + public Stack<HashMap<String, Type>> _types; + + + // A new ScopeNode + public ScopeNode() { + _scopes = new Stack<>(); + _types = new Stack<>(); + _type = Type.BOTTOM; + } + + + @Override public String label() { return "Scope"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("Scope[ "); + String[] names = reverseNames(); + for( int j=0; j<nIns(); j++ ) { + sb.append(names[j]).append(":"); + Node n = in(j); + while( n instanceof ScopeNode loop ) { + sb.append("Lazy_"); + n = loop.in(j); + } + n._print0(sb, visited).append(" "); + } + sb.setLength(sb.length()-1); + return sb.append("]"); + } + + /** + * Recover the names for all variable bindings. + * The result is an array of names that is aligned with the + * inputs to the Node. + * + * This is an expensive operation. + */ + public String[] reverseNames() { + String[] names = new String[nIns()]; + for( HashMap<String,Integer> syms : _scopes ) + for( String name : syms.keySet() ) + names[syms.get(name)] = name; + return names; + } + + @Override public Type compute() { return Type.BOTTOM; } + + @Override public Node idealize() { return null; } + + public void push() { _scopes.push(new HashMap<>()); _types.push(new HashMap<>()); } + public void pop() { popN(_scopes.pop().size()); _types.pop(); } + + /** + * Create a new name in the current scope + */ + public Node define( String name, Type declaredType, Node n ) { + HashMap<String,Integer> syms = _scopes.lastElement(); + _types.lastElement().put(name,declaredType); + if( syms.put(name,nIns()) != null ) + return null; // Double define + return addDef(n); + } + /** + * Lookup a name in all scopes starting from most deeply nested. + * + * @param name Name to be looked up + * @see #update(String, Node, int) + */ + public Node lookup( String name ) { return update(name,null,_scopes.size()-1); } + /** + * If the name is present in any scope, then redefine else null + * + * @param name Name being redefined + * @param n The node to bind to the name + */ + public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); } + /** + * Both recursive lookup and update. + * <p> + * A shared implementation allows us to create lazy phis both during + * lookups and updates; the lazy phi creation is part of chapter 8. + * + * @param name Name whose binding is being updated or looked up + * @param n If null, do a lookup, else update binding + * @param nestingLevel The starting nesting level + * @return node being looked up, or the one that was updated + */ + private Node update( String name, Node n, int nestingLevel ) { + if( nestingLevel<0 ) return null; // Missed in all scopes, not found + var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level + var idx = syms.get(name); + if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope + Node old = in(idx); + if( old instanceof ScopeNode loop ) { + // Lazy Phi! + old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() + // Loop already has a real Phi, use it + ? loop.in(idx) + // Set real Phi in the loop head + // The phi takes its one input (no backedge yet) from a recursive + // lookup, which might have insert a Phi in every loop nest. + : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + setDef(idx,old); + } + return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer + } + + // Return declared type + public Type lookupDeclaredType( String name ) { + for( int i=_types.size(); i>0; i-- ) { + Type t = _types.get(i-1).get(name); + if( t != null ) return t; + } + return null; + } + + public Node ctrl() { return in(0); } + + /** + * The ctrl of a ScopeNode is always bound to the currently active + * control node in the graph, via a special name '$ctrl' that is not + * a valid identifier in the language grammar and hence cannot be + * referenced in Simple code. + * + * @param n The node to be bound to '$ctrl' + * + * @return Node that was bound + */ + public Node ctrl(Node n) { return setDef(0,n); } + + /** + * Duplicate a ScopeNode; including all levels, up to Nodes. So this is + * neither shallow (would dup the Scope but not the internal HashMap + * tables), nor deep (would dup the Scope, the HashMap tables, but then + * also the program Nodes). + * <p> + * If the {@code loop} flag is set, the edges are filled in as the original + * Scope, as a indication of Lazy Phis at loop heads. The goal here is to + * not make Phis at loop heads for variables which are never touched in the + * loop body. + * <p> + * The new Scope is a full-fledged Node with proper use<->def edges. + */ + public ScopeNode dup() { return dup(false); } + public ScopeNode dup(boolean loop) { + ScopeNode dup = new ScopeNode(); + // Our goals are: + // 1) duplicate the name bindings of the ScopeNode across all stack levels + // 2) Make the new ScopeNode a user of all the nodes bound + // 3) Ensure that the order of defs is the same to allow easy merging + for( HashMap<String,Integer> syms : _scopes ) + dup._scopes.push(new HashMap<>(syms)); + for( HashMap<String,Type> ts : _types ) + dup._types.push(ts); // Types don't change, just keep stacks aligned + + dup.addDef(ctrl()); // Control input is just copied + for( int i=1; i<nIns(); i++ ) + // For lazy phis on loops we use a sentinel + // that will trigger phi creation on update + dup.addDef(loop ? this : in(i)); + return dup; + } + + /** + * Merges the names whose node bindings differ, by creating Phi node for such names + * The names could occur at all stack levels, but a given name can only differ in the + * innermost stack level where the name is bound. + * + * @param that The ScopeNode to be merged into this + * @return A new node representing the merge point + */ + public Node mergeScopes(ScopeNode that) { + RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); + String[] ns = reverseNames(); + // Note that we skip i==0, which is bound to '$ctrl' + for (int i = 1; i < nIns(); i++) + if( in(i) != that.in(i) ) // No need for redundant Phis + // If we are in lazy phi mode we need to a lookup + // by name as it will trigger a phi creation + setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + that.kill(); // Kill merged scope + IterPeeps.add(r); + return r.unkeep().peephole(); + } + + // Merge the backedge scope into this loop head scope + // We set the second input to the phi from the back edge (i.e. loop body) + public void endLoop(ScopeNode back, ScopeNode exit ) { + Node ctrl = ctrl(); + assert ctrl instanceof LoopNode loop && loop.inProgress(); + ctrl.setDef(2,back.ctrl()); + for( int i=1; i<nIns(); i++ ) { + if( back.in(i) != this ) { + PhiNode phi = (PhiNode)in(i); + assert phi.region()==ctrl && phi.in(2)==null; + phi.setDef(2,back.in(i)); + } + if( exit.in(i) == this ) // Replace a lazy-phi on the exit path also + exit.setDef(i,in(i)); + } + back.kill(); // Loop backedge is dead + // Now one-time do a useless-phi removal + for( int i=1; i<nIns(); i++ ) { + 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 + } + } + } + + } + + + // Up-casting: using the results of an If to improve a value. + // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null. + + // This Scope looks for direct variable uses, or certain simple + // combinations, and replaces the variable with the upcast variant. + public Node upcast( Node ctrl, Node pred, boolean invert ) { + if( ctrl._type==Type.XCONTROL ) return null; + // Invert the If conditional + if( invert ) + pred = pred instanceof NotNode not ? not.in(1) : IterPeeps.add(new NotNode(pred).peephole()); + + // Direct use of a value as predicate. This is a zero/null test. + if( Utils.find(_inputs, pred) != -1 ) { + if( !(pred._type instanceof TypeMemPtr tmp) ) + // Must be an `int`, since int and ptr are the only two value types + // being tested. No representation for a generic not-null int, so no upcast. + return null; + if( tmp.isa(TypeMemPtr.VOIDPTR) ) + return null; // Already not-null, no reason to upcast + // Upcast the ptr to not-null ptr, and replace in scope + return replace(pred,new CastNode(TypeMemPtr.VOIDPTR,ctrl,pred).peephole()); + } + + if( pred instanceof NotNode not ) { + // Direct use of a !value as predicate. This is a zero/null test. + if( Utils.find(_inputs, not.in(1)) != -1 ) { + Type tinit = not.in(1)._type.makeInit(); + if( not.in(1)._type.isa(tinit) ) return null; // Already zero/null, no reason to upcast + return replace(not.in(1), new ConstantNode(tinit).peephole()); + } + } + // Apr/9/2024: Attempted to replace X with Y if guarded by a test of + // X==Y. This didn't seem to help very much, or at least in the test + // cases seen so far was a very minor help. + + // No upcast + return null; + } + + private Node replace( Node old, Node cast ) { + assert old!=null && old!=cast; + for( int i=0; i<nIns(); i++ ) + if( in(i)==old ) + setDef(i,cast); + return cast; + } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java new file mode 100644 index 00000000..4365c608 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -0,0 +1,77 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashSet; + +/** + * The Start node represents the start of the function. + * + * Start initially has 1 input (arg) from outside and the initial control. + * In ch10 we also add mem aliases as structs get defined; each field in struct + * adds a distinct alias to Start's tuple. + */ +public class StartNode extends CFGNode implements MultiNode { + + // This field is NOT final, because the tuple expands as we add memory aliases + TypeTuple _args; + + public StartNode(Type[] args) { + super(); + _args = TypeTuple.make(args); + _type = _args; + } + + /** + * Creates a projection for each of the struct's fields, using the field alias + * as the key. + */ + public void addMemProj( TypeStruct ts, ScopeNode scope) { + if( ts._fields.length==0 ) return; // No projections to add + int len = _args._types.length; + int max = ts._fields[ts._fields.length-1]._alias; // Max alias + if( len > max ) return; // Already big enough + // resize the tuple's type array to include all fields of the struct + Type[] args = Arrays.copyOf(_args._types, max+1); + // The new members of the tuple get a mem type with an alias + for( int alias = len; alias <= max; alias++ ) + args[alias] = TypeMem.make(alias); + _type = _args = TypeTuple.make(args); + // For each of the fields we now add a mem projection. Note that the + // alias matches the slot of the field in the tuple + for( int alias = len; alias <= max; alias++ ) { + String name = "$"+alias; + Node n = new ProjNode(this, alias, name).peephole(); + scope.define(name, args[alias], n); + } + } + + @Override + public String label() { return "Start"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()); + } + + @Override public boolean isMultiHead() { return true; } + @Override public boolean blockHead() { return true; } + + @Override + public TypeTuple compute() { return _args; } + + @Override + public Node idealize() { return null; } + + // No immediate dominator, and idepth==0 + @Override public int idepth() { return 0; } + @Override public CFGNode idom() { return null; } + + @Override void _walkUnreach( BitSet visit, HashSet<CFGNode> unreach ) { } + + @Override public int loopDepth() { return (_loopDepth=1); } + + @Override public Node getBlockStart() { return this; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java new file mode 100644 index 00000000..94b2ad6d --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -0,0 +1,79 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.IterPeeps; + +import java.util.BitSet; + +public class StopNode extends CFGNode { + + public final String _src; + + public StopNode(String src) { + super(); + _src = src; + } + + @Override + public String label() { + return "Stop"; + } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( ret()!=null ) return ret()._print0(sb, visited); + sb.append("Stop[ "); + for( Node ret : _inputs ) + ret._print0(sb, visited).append(" "); + return sb.append("]"); + } + + @Override public boolean blockHead() { return true; } + + // If a single Return, return it. + // Otherwise, null because ambiguous. + public ReturnNode ret() { + return nIns()==1 ? (ReturnNode)in(0) : null; + } + + @Override + public Type compute() { + return Type.BOTTOM; + } + + @Override + public Node idealize() { + int len = nIns(); + for( int i=0; i<nIns(); i++ ) + if( in(i)._type==Type.XCONTROL ) + delDef(i--); + if( len != nIns() ) return this; + return null; + } + + @Override public int idepth() { + if( _idepth!=0 ) return _idepth; + int d=0; + for( Node n : _inputs ) + if( n!=null ) + d = Math.max(d,((CFGNode)n).idepth()+1); + return _idepth=d; + } + @Override public CFGNode idom() { return null; } + + @Override public int loopDepth() { return (_loopDepth=1); } + + public Node addReturn(Node node) { + return addDef(node); + } + + public StopNode iterate( ) { return IterPeeps.iterate(this,false).typeCheck(); } + public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ).typeCheck(); } + StopNode typeCheck() { + String err = walk( Node::err ); + if( err != null ) throw new RuntimeException(err); + return this; + } + + @Override public Node getBlockStart() { return this; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java new file mode 100644 index 00000000..00d75f02 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -0,0 +1,71 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +/** + * Store represents setting a value to a memory based object, in chapter 10 + * this means a field inside a struct. + */ +public class StoreNode extends MemOpNode { + + /** + * @param field The struct field we are assigning to + * @param memSlice The memory alias node - this is updated after a Store + * @param memPtr The ptr to the struct where we will store a value + * @param value Value to be stored + */ + public StoreNode(Field field, Node memSlice, Node memPtr, Node value) { + super(field, memSlice, memPtr, value); + } + + @Override + public String label() { return "Store"; } + @Override + public String glabel() { return "."+_field._fname+" ="; } + @Override + public boolean isMem() { return true; } + public Node val() { return in(3); } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(".").append(_field._fname).append("=").append( val()).append(";"); + } + + @Override + public Type compute() { + return TypeMem.make(_field._alias); + } + + @Override + public Node idealize() { + + // Simple store-after-store on same address. Should pick up the + // required init-store being stomped by a first user store. + if( mem() instanceof StoreNode st && + ptr()==st.ptr() && // Must check same object + ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers + // Must have exactly one use of "this" or you get weird + // non-serializable memory effects in the worse case. + st.checkNoUseBeyond(this) ) { + assert _field==st._field; // Equiv class aliasing is perfect + setDef(1,st.mem()); + return this; + } + + return null; + } + + // Check that `this` has no uses beyond `that` + private boolean checkNoUseBeyond(Node that) { + if( nOuts()==1 ) return true; + // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that + // when the other uses go away we can retry. + for( Node use : _outputs ) + if( use != that ) + use.addDep(that); + return false; + } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java new file mode 100644 index 00000000..663b005c --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -0,0 +1,43 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + +import java.util.BitSet; + +public class SubNode extends Node { + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Sub"; } + + @Override public String glabel() { return "-"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("-"), visited); + return sb.append(")"); + } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeInteger i0 && + in(2)._type instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()-i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize() { + // Sub of same is 0 + if( in(1)==in(2) ) + return Parser.ZERO; + + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java new file mode 100644 index 00000000..83779dd2 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -0,0 +1,13 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import java.util.BitSet; + +public class XCtrlNode extends CFGNode { + public XCtrlNode( ) { super(Parser.START); } + @Override public String label() { return "Xctrl"; } + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); } + @Override public Type compute() { return Type.XCONTROL; } + @Override public Node idealize() { return null; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java new file mode 100644 index 00000000..179eacf5 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/Field.java @@ -0,0 +1,96 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Represents a field in a struct. This is not a Type in the type system. + */ +public class Field extends Type { + + /** + * Represents the starting alias ID - which is 2 because then it nicely + * slots into Start's projections. Start already uses slots 0-1. + */ + private static final int _RESET_ALIAS_ID = 2; + + /** + * Alias ID generator - we start at 2 because START uses 0 and 1 slots, + * by starting at 2, our alias ID is nicely mapped to a slot in Start. + */ + private static int _ALIAS_ID = _RESET_ALIAS_ID; + + // The triple {structName,type,fieldName} uniquely identifies a field. + + // Struct name + public final String _sname; + // Type of the field + public final Type _type; + // Field name + public final String _fname; + + // Convenience field: Owning TypeStruct + public TypeStruct _obj; + + // Convenience field: unique alias number + public final int _alias; + + private Field(String sname, Type type, String fname, int alias) { + super(TFLD); + _sname = sname; + _type = type; + _fname = fname; + _alias = alias; + } + // Make with existing alias + private static Field make( String sname, Type type, String fname, int alias ) { + return new Field(sname,type,fname,alias).intern(); + } + // Make with new alias + public static Field make( String sname, Type type, String fname ) { + Field fld = make(sname,type,fname,_ALIAS_ID).intern(); + if( fld._alias == _ALIAS_ID ) + _ALIAS_ID++; + return fld; + } + + public static final Field TEST = make("test",TypeInteger.ZERO,"test"); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); } + + @Override Field xmeet( Type that ) { + Field fld = (Field)that; // Invariant + assert _sname.equals(fld._sname); + assert _fname.equals(fld._fname); + return make(_sname,_type.meet(fld._type),_fname); + } + + @Override + public Field dual() { return make(_sname,_type.dual(),_fname,_alias); } + + public String aliasName() { return "$"+_alias; }; + + @Override + public Field glb() { + return make(_sname,_type.glb(),_fname,_alias); + } + + // Override in subclasses + int hash() { return _sname.hashCode() ^ _type.hashCode() ^ _fname.hashCode(); } + + boolean eq(Type t) { + Field f = (Field)t; + return _sname.equals(f._sname) && _type==f._type && _fname.equals(f._fname); + } + + + @Override + public StringBuilder _print( StringBuilder sb ) { + return _type._print(sb.append(_fname).append(":")); + } + + @Override public String str() { return _fname; } + + static void resetField() { + _ALIAS_ID = _RESET_ALIAS_ID; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter11/src/main/java/com/seaofnodes/simple/type/Type.java new file mode 100644 index 00000000..34e66a0c --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/Type.java @@ -0,0 +1,191 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * These types are part of a Monotone Analysis Framework, + * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>. + * <p> + * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a> + * <p> + * This wild lattice theory will be needed later to allow us to easily beef up + * the analysis and optimization of the Simple compiler... but we don't need it + * now, just know that it is coming along in a later Chapter. + * <p>g + * One of the fun things here is that while the theory is deep and subtle, the + * actual implementation is darn near trivial and is generally really obvious + * what we're doing with it. Right now, it's just simple integer math to do + * simple constant folding e.g. 1+2 == 3 stuff. + */ + +public class Type { + static final HashMap<Type,Type> INTERN = new HashMap<>(); + + // ---------------------------------------------------------- + // Simple types are implemented fully here. "Simple" means: the code and + // type hierarchy are simple, not that the Type is conceptually simple. + static final byte TBOT = 0; // Bottom (ALL) + static final byte TTOP = 1; // Top (ANY) + static final byte TCTRL = 2; // Ctrl flow bottom + static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) + static final byte TSIMPLE = 4; // End of the Simple Types + static final byte TINT = 5; // All Integers; see TypeInteger + static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias + static final byte TMEMPTR = 8; // Memory pointer type + static final byte TSTRUCT = 9; // Structs; tuples with named fields + static final byte TFLD =10; // Fields into struct + + public final byte _type; + + public boolean is_simple() { return _type < TSIMPLE; } + private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; + protected Type(byte type) { _type = type; } + + public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL + public static final Type TOP = new Type( TTOP ).intern(); // ANY + public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl + public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl + public static Type[] gather() { + ArrayList<Type> ts = new ArrayList<>(); + ts.add(BOTTOM); + ts.add(CONTROL); + Field.gather(ts); + TypeInteger.gather(ts); + TypeMem.gather(ts); + TypeMemPtr.gather(ts); + TypeStruct.gather(ts); + TypeTuple.gather(ts); + int sz = ts.size(); + for( int i = 0; i < sz; i++ ) + ts.add(ts.get(i).dual()); + return ts.toArray(new Type[ts.size()]); + } + + // Is high or on the lattice centerline. + public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } + + // Strict constant values, things on the lattice centerline. + // Excludes both high and low values + public boolean isConstant() { return false; } + + /** + * Display Type name in a format that's good for IR printer + */ + public StringBuilder typeName( StringBuilder sb) { return _print(sb); } + + public Type makeInit() { return null; } + + // ---------------------------------------------------------- + + // Notes on Type interning. + // At the moment it is not easy to reset the interned types + // because we hold static references to several types and these are scattered + // around. This means the INTERN cache will retain all types from + // every run of the Parser. For this to work correctly types must be + // rigorous about defining when they are the same. Also types need to be + // immutable once defined. + // The rationale for interning is performance. + + // Factory method which interns "this" + public <T 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; + // Same-type is always safe in the subclasses + if( _type==t._type ) return xmeet(t); + // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". + if( is_simple() ) return this.xmeet(t ); + if( t.is_simple() ) return t .xmeet(this); + return Type.BOTTOM; // Mixing 2 unrelated types + } + + // Compute meet right now. Overridden in subclasses. + // Handle cases where 'this.is_simple()' and unequal to 't'. + // Subclassed xmeet calls can assert that '!t.is_simple()'. + Type xmeet(Type t) { + assert is_simple(); // Should be overridden in subclass + // ANY meet anything is thing; thing meet ALL is ALL + if( _type==TBOT || t._type==TTOP ) return this; + if( _type==TTOP || t._type==TBOT ) return t; + // 'this' is {TCTRL,TXCTRL} + if( !t.is_simple() ) return BOTTOM; + // 't' is {TCTRL,TXCTRL} + return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; + } + + public Type dual() { + return switch( _type ) { + case TBOT -> TOP; + case TTOP -> BOTTOM; + case TCTRL -> XCONTROL; + case TXCTRL -> CONTROL; + default -> throw Utils.TODO(); // Should not reach here + }; + } + + // ---------------------------------------------------------- + // Our lattice is defined with a MEET and a DUAL. + // JOIN is dual of meet of both duals. + public final Type join(Type t) { + if( this==t ) return this; + return dual().meet(t.dual()).dual(); + } + + // True if this "isa" t; e.g. 17 isa TypeInteger.BOT + public boolean isa( Type t ) { return meet(t)==t; } + + /** + * Compute greatest lower bound in the lattice + */ + public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; } + + // ---------------------------------------------------------- + // Useful in the debugger, which calls toString everywhere. + // This is a more verbose dev-friendly print. + @Override + public final String toString() { + return _print(new StringBuilder()).toString(); + } + + public StringBuilder _print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} + + // This is used by error messages, and is a shorted print. + public String str() { return STRS[_type]; } + + public static void reset() { + Field.resetField(); + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeInteger.java new file mode 100644 index 00000000..f2a93a3a --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -0,0 +1,101 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** + * Integer Type + */ +public class TypeInteger extends Type { + + public final static TypeInteger TOP = make(false, 0); + public final static TypeInteger BOT = make(false, 1); + public final static TypeInteger ZERO= make(true , 0); + + public final boolean _is_con; + + /** + * The constant value or + * if not constant then 1=bottom, 0=top. + */ + public final long _con; + + private TypeInteger(boolean is_con, long con) { + super(TINT); + _is_con = is_con; + _con = con; + } + public static TypeInteger make(boolean is_con, long con) { + return new TypeInteger(is_con,con).intern(); + } + + public static TypeInteger constant(long con) { return make(true, con); } + + public static void gather(ArrayList<Type> ts) { ts.add(ZERO); ts.add(BOT); } + + // FIXME this display format is problematic + // In visualizer '#' gets prepended if its a constant + @Override + public StringBuilder _print(StringBuilder sb) { + if( this==TOP ) return sb.append("IntTop"); + if( this==BOT ) return sb.append("IntBot"); + return sb.append(_con); + } + + @Override public String str() { + if( this==TOP ) return "~int"; + if( this==BOT ) return "int"; + return ""+_con; + } + + /** + * Display Type name in a format that's good for IR printer + */ + @Override + public StringBuilder typeName(StringBuilder sb) { + if( this==TOP ) return sb.append("IntTop"); + if( this==BOT ) return sb.append("IntBot"); + return sb.append("Int"); + } + + @Override + public boolean isHighOrConst() { return _is_con || _con==0; } + + @Override + public boolean isConstant() { return _is_con; } + + public long value() { return _con; } + + @Override + public Type xmeet(Type other) { + // Invariant from caller: 'this' != 'other' and same class (TypeInteger) + TypeInteger i = (TypeInteger)other; // Contract + // BOT wins + if ( this==BOT ) return this; + if ( i ==BOT ) return i ; + // TOP loses + if ( i ==TOP ) return this; + if ( this==TOP ) return i ; + // Since both are constants, and are never equals (contract) unequals + // constants fall to bottom + return BOT; + } + + @Override + public Type dual() { + if( isConstant() ) return this; // Constants are a self-dual + return _con==0 ? BOT : TOP; + } + + @Override + public Type glb() { return BOT; } + + @Override public TypeInteger makeInit() { return ZERO; } + + @Override + int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); } + @Override + public boolean eq( Type t ) { + TypeInteger i = (TypeInteger)t; // Contract + return _con==i._con && _is_con==i._is_con; + } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMem.java new file mode 100644 index 00000000..198d49ee --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; +import java.lang.Long; +import java.util.BitSet; +import java.util.ArrayList; + + +/** + * Represents a slice of memory corresponding to a set of aliases + */ +public class TypeMem extends Type { + + // Which slice of memory? + // 0 means TOP, no slice. + // -1 means BOT, all memory. + // N means slice#N. + private final int _alias; + + private TypeMem(int alias) { super(TMEM); _alias = alias; } + + public static TypeMem make(int alias) { return new TypeMem(alias).intern(); } + public static final TypeMem TOP = make( 0); + public static final TypeMem BOT = make(-1); + + + public static void gather(ArrayList<Type> ts) { ts.add(make(1)); ts.add(BOT); } + + @Override + TypeMem xmeet(Type t) { + TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal + return _alias==0 ? that : (that._alias==0 ? this : BOT); + } + + @Override + public Type dual() { + if( _alias== 0 ) return BOT; + if( _alias==-1 ) return TOP; + return this; + } + + @Override + public Type glb() { return TypeMem.BOT; } + + @Override + int hash() { return 9876543 + _alias; } + + @Override + boolean eq(Type t) { + TypeMem that = (TypeMem) t; // Invariant + return _alias == that._alias; + } + + @Override + public StringBuilder _print(StringBuilder sb) { + return sb.append("MEM#").append( switch(_alias) { + case 0 -> "TOP"; + case -1 -> "BOT"; + default -> ""+_alias; + }); + } + + @Override public String str() { return _print(new StringBuilder()).toString(); } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java new file mode 100644 index 00000000..65352402 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -0,0 +1,78 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** + * Represents a Pointer to memory. + * + * Null is generic pointer to non-existent memory. + * *void is a non-Null pointer to all possible structs. + * Pointers can be to specific struct types, or a union with Null. + * The distinguished *$BOT ptr represents union of *void and Null. + * The distinguished *$TOP ptr represents the dual of *$BOT. + */ +public class TypeMemPtr extends Type { + // A TypeMemPtr is pair (obj,nil) + // where obj is one of + // (null,TypeStruct). + // where nil is one of + // (true,false) meaning an explicit null is allowed or not + + public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY + public final boolean _nil; + + private TypeMemPtr(TypeStruct obj, boolean nil) { + super(TMEMPTR); + _obj = obj; + _nil = nil; + } + public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } + public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); } + + public static TypeMemPtr BOT = make(TypeStruct.BOT,true); + public static TypeMemPtr TOP = BOT.dual(); + public static TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); + public static TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null + public static TypeMemPtr TEST= make(TypeStruct.TEST,false); + public static void gather(ArrayList<Type> ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); } + + @Override + Type xmeet(Type t) { + TypeMemPtr that = (TypeMemPtr) t; + return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil); + } + + @Override + public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); } + + @Override + public Type glb() { + if( _obj==null ) return BOT; + return make(_obj.glb(),true); + } + @Override public TypeMemPtr makeInit() { return NULLPTR; } + + @Override + int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); } + + @Override + boolean eq(Type t) { + TypeMemPtr ptr = (TypeMemPtr)t; // Invariant + return _obj == ptr._obj && _nil == ptr._nil; + } + + // [void,name,MANY]*[,?] + @Override + public StringBuilder _print(StringBuilder sb) { + if( this== NULLPTR) return sb.append("null"); + if( this== VOIDPTR) return sb.append("*void"); + return _obj._print(sb.append("*")).append(_nil ? "?" : ""); + } + + @Override public String str() { + if( this== NULLPTR) return "null"; + if( this== VOIDPTR) return "*void"; + return "*"+_obj.str()+(_nil ? "?" : ""); + } + +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeStruct.java new file mode 100644 index 00000000..4fcbb638 --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -0,0 +1,123 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Represents a struct type. + */ +public class TypeStruct extends Type { + + // For now in terms of the lattice + // a struct type just stays as is + + public final String _name; + public final Field[] _fields; + + private TypeStruct(String name, Field[] fields) { + super(Type.TSTRUCT); + _name = name; + _fields = fields; + for( Field f : _fields ) + f._obj = this; + } + + public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); } + public static TypeStruct make(String name, ArrayList<Field> fields) { return make(name,fields.toArray(new Field[fields.size()])); } + + public static final TypeStruct TOP = make("$TOP",new Field [0]); + public static final TypeStruct BOT = make("$BOT",new Field [0]); + public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); } + + public Field get(String fname) { + for( Field f : _fields ) + if( f._fname.equals(fname) ) + return f; + return null; + } + + @Override + Type xmeet(Type t) { + TypeStruct that = (TypeStruct) t; + if( this==TOP ) return that; + if( that==TOP ) return this; + if( this==BOT ) return BOT; + if( that==BOT ) return BOT; + + // Within the same compilation unit, struct names are unique. If the + // names differ, its different structs. Across many compilation units, + // structs with the same name but different field layouts can be + // interned... which begs the question: + // "What is the meet of structs from two different compilation units?" + // And the answer is: "don't ask". + if( !_name.equals(that._name) ) + return BOT; // It's a struct; that's about all we know + // Now all fields should be the same, so just do field meets + assert _fields.length == that._fields.length; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = (Field)_fields[i].meet(that._fields[i]); + return make(_name,flds); + } + + @Override + public TypeStruct dual() { + if( this==TOP ) return BOT; + if( this==BOT ) return TOP; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].dual(); + return make(_name,flds); + } + + // Keeps the same struct, but lower-bounds all fields. + @Override + public TypeStruct glb() { + if( _glb() ) return this; + // Need to glb each field + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].glb(); + return make(_name,flds); + } + private boolean _glb() { + for( Field f : _fields ) + if( f.glb() != f ) + return false; + return true; + } + + @Override + boolean eq(Type t) { + TypeStruct ts = (TypeStruct)t; // Invariant + if( !_name.equals(ts._name) ) + return false; + if( _fields.length!=ts._fields.length ) + return false; + for( int i = 0; i < _fields.length; i++ ) + if( _fields[i] != ts._fields[i] ) + return false; + return true; + } + + @Override + int hash() { + long hash = _name.hashCode(); + for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode(); + return Utils.fold(hash); + } + + @Override + public StringBuilder _print(StringBuilder sb) { + sb.append(_name).append(" {"); + for( Field f : _fields ) { + sb.append(" ").append(f._fname).append(":"); + f._type._print(sb); + sb.append(";"); + } + return sb.append("}"); + } + + @Override public String str() { return _name; } +} diff --git a/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java new file mode 100644 index 00000000..f522542d --- /dev/null +++ b/chapter11/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -0,0 +1,87 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +public class TypeTuple extends Type { + + public final Type[] _types; + + private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } + public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } + + private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); } + + @Override + Type xmeet(Type other) { + TypeTuple tt = (TypeTuple)other; // contract from xmeet + assert _types.length == tt._types.length; + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].meet(tt._types[i]); + return make(ts); + } + + @Override public Type dual() { + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].dual(); + return make(ts); + } + + @Override + public Type glb() { + Type[] ts = new Type[_types.length]; + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i].glb(); + return make(ts); + } + + @Override + public StringBuilder _print(StringBuilder sb) { + sb.append("["); + for( Type t : _types ) + t._print(sb).append(","); + sb.setLength(sb.length()-1); + return sb.append("]"); + } + + @Override public String str() { return _print(new StringBuilder()).toString(); } + + /** + * Display Type name in a format that's good for IR printer + */ + @Override + public StringBuilder typeName(StringBuilder sb) { + sb.append("["); + for( Type t : _types ) + t.typeName(sb).append(","); + sb.setLength(sb.length()-1); + sb.append("]"); + return sb; + } + + public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); + public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); + public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); + public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); + + @Override + int hash() { + int sum = 0; + for( Type type : _types ) sum ^= type.hashCode(); + return sum; + } + + @Override + boolean eq( Type t ) { + TypeTuple tt = (TypeTuple)t; // Contract + if( _types.length != tt._types.length ) return false; + for( int i=0; i<_types.length; i++ ) + if( _types[i]!=tt._types[i] ) + return false; + return true; + } + + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java new file mode 100644 index 00000000..81b0d173 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +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 Chapter01Test { + + @Test + public void testSimpleProgram() { + Parser parser = new Parser("return 1;"); + StopNode stop = parser.parse(); + StartNode start = Parser.START; + ReturnNode ret = (ReturnNode)stop.in(0); + + assertTrue(ret.ctrl() instanceof CProjNode); + Node expr = ret.expr(); + if( expr instanceof ConstantNode con ) { + assertEquals(start,con.in(0)); + assertEquals(TypeInteger.constant(1), con._type); + } else { + fail(); + } + } + + @Test + public void testZero() { + Parser parser = new Parser("return 0;"); + parser.parse(); + StartNode start = Parser.START; + for( Node use : start._outputs ) + if( use instanceof ConstantNode con && con._type instanceof TypeInteger ) + assertEquals(TypeInteger.constant(0),con._type); + } + + + @Test + public void testBad2() { + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter02Test.java new file mode 100644 index 00000000..351cdb16 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -0,0 +1,99 @@ +package com.seaofnodes.simple; + +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 Chapter02Test { + + @Test + public void testParseGrammar() { + 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()); + GraphVisualizer gv = new GraphVisualizer(); + System.out.println(gv.generateDotOutput(parser)); + Node._disablePeephole = false; + } + + @Test + public void testAddPeephole() { + Parser parser = new Parser("return 1+2;"); + StopNode ret = parser.parse(); + assertEquals("return 3;", ret.print()); + } + + @Test + public void testSubPeephole() { + Parser parser = new Parser("return 1-2;"); + StopNode ret = parser.parse(); + assertEquals("return -1;", ret.print()); + } + + @Test + public void testMulPeephole() { + Parser parser = new Parser("return 2*3;"); + StopNode ret = parser.parse(); + assertEquals("return 6;", ret.print()); + } + + @Test + public void testDivPeephole() { + Parser parser = new Parser("return 6/3;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @Test + public void testMinusPeephole() { + Parser parser = new Parser("return 6/-3;"); + StopNode ret = parser.parse(); + assertEquals("return -2;", ret.print()); + } + + @Test + public void testExample() { + Parser parser = new Parser("return 1+2*3+-5;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + GraphVisualizer gv = new GraphVisualizer(); + System.out.println(gv.generateDotOutput(parser)); + } + + @Test + public void testBad1() { + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'ret'",e.getMessage()); + } + } + @Test + public void testNotBad3() { + // this test used to fail in chapter 1 + assertEquals("return 12;", new Parser("return --12;").parse().print()); + } + + @Test + public void testBad4() { + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } + } + + @Test + public void testNotBad5() { + // this test used to fail in chapter 1 + assertEquals("return -100;", new Parser("return -100;").parse().print()); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter03Test.java new file mode 100644 index 00000000..8a11107d --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -0,0 +1,70 @@ +package com.seaofnodes.simple; + +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 Chapter03Test { + + @Test + public void testVarDecl() { + Parser parser = new Parser("int a=1; return a;"); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testVarAdd() { + Parser parser = new Parser("int a=1; int b=2; return a+b;"); + StopNode ret = parser.parse(); + assertEquals("return 3;", ret.print()); + } + + @Test + public void testVarScope() { + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;"); + StopNode ret = parser.parse(); + assertEquals("return 4;", ret.print()); + } + + @Test + public void testVarScopeNoPeephole() { + Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;"); + Node._disablePeephole = true; + StopNode ret = parser.parse(); + Node._disablePeephole = false; + assertEquals("return (1+3);", ret.print()); + } + + @Test + public void testVarDist() { + Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return 8;", ret.print()); + } + + @Test + public void testSelfAssign() { + try { + new Parser("int a=a; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'a'",e.getMessage()); + } + } + + @Test + public void testBad6() { + try { + new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected }: ",e.getMessage()); + } + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java new file mode 100644 index 00000000..57d464ef --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -0,0 +1,130 @@ +package com.seaofnodes.simple; + +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 Chapter04Test { + + @Test + public void testPeephole() { + Parser parser = new Parser("return 1+arg+2; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testPeephole2() { + Parser parser = new Parser("return (1+arg)+2;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testAdd0() { + Parser parser = new Parser("return 0+arg;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void testAddAddMul() { + Parser parser = new Parser("return arg+0+arg;"); + StopNode ret = parser.parse(); + assertEquals("return (arg*2);", ret.print()); + } + + @Test + public void testPeephole3() { + Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return ((arg*2)+6);", ret.print()); + } + + @Test + public void testMul1() { + Parser parser = new Parser("return 1*arg;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void testVarArg() { + Parser parser = new Parser("return arg; #showGraph;"); + StopNode stop = parser.parse(); + ReturnNode ret = stop.ret(); + assertTrue(ret.in(0) instanceof CProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + } + + @Test + public void testConstantArg() { + Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2)); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @Test + public void testCompEq() { + Parser parser = new Parser("return 3==3; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testCompEq2() { + Parser parser = new Parser("return 3==4; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return 0;", ret.print()); + } + + @Test + public void testCompNEq() { + Parser parser = new Parser("return 3!=3; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return 0;", ret.print()); + } + + @Test + public void testCompNEq2() { + Parser parser = new Parser("return 3!=4; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testBug1() { + Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testBug2() { + Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+1);", ret.print()); + } + + @Test + public void testBug3() { + try { + new Parser("inta=1; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'inta'",e.getMessage()); + } + } + + @Test + public void testBug4() { + Parser parser = new Parser("return -arg;"); + StopNode ret = parser.parse(); + assertEquals("return (-arg);", ret.print()); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java new file mode 100644 index 00000000..2c17113e --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -0,0 +1,225 @@ +package com.seaofnodes.simple; + +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 Chapter05Test { + + @Test + public void testIfStmt() { + Parser parser = new Parser( +""" +int a = 1; +if (arg == 1) + a = arg+2; +else { + a = arg-3; + #showGraph; +} +#showGraph; +return a;"""); + StopNode ret = parser.parse().iterate(true); + assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + } + + @Test + public void testTest() { + Parser parser = new Parser( +""" +int c = 3; +int b = 2; +if (arg == 1) { + b = 3; + c = 4; +} +return c;""", TypeInteger.BOT); + StopNode ret = parser.parse().iterate(true); + assertEquals("return Phi(Region18,4,3);", ret.toString()); + } + + @Test + public void testReturn2() { + Parser parser = new Parser( +""" +if( arg==1 ) + return 3; +else + return 4; +#showGraph;""", TypeInteger.BOT); + StopNode stop = parser.parse(); + assertEquals("Stop[ return 3; return 4; ]", stop.toString()); + } + + @Test + public void testIfMergeB() { + Parser parser = new Parser( +""" +int a=arg+1; +int b=0; +if( arg==1 ) + b=a; +else + b=a+1; +return a+b;"""); + StopNode ret = parser.parse().iterate(true); + assertEquals("return ((arg*2)+Phi(Region23,2,3));", ret.toString()); + } + + @Test + public void testIfMerge2() { + Parser parser = new Parser( +""" +int a=arg+1; +int b=arg+2; +if( arg==1 ) + b=b+a; +else + a=b+1; +return a+b;"""); + StopNode ret = parser.parse().iterate(true); + assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + } + + @Test + public void testMerge3() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( arg==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +#showGraph;""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + } + + @Test + public void testMerge4() { + Parser parser = new Parser( +""" +int a=0; +int b=0; +if( arg ) + a=1; +if( arg==0 ) + b=2; +return arg+a+b; +#showGraph;""", TypeInteger.BOT); + StopNode stop = parser.parse(); + assertEquals("return ((arg+Phi(Region15,1,0))+Phi(Region30,2,0));", stop.toString()); + } + + @Test + public void testMerge5() { + Parser parser = new Parser( +""" +int a=arg==2; +if( arg==1 ) +{ + a=arg==3; +} +return a;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return (arg==Phi(Region18,3,2));", ret.toString()); + } + + @Test + public void testTrue() { + StopNode stop = new Parser("return true;").parse(); + assertEquals("return 1;",stop.toString()); + } + + @Test + public void testHalfDef() { + try { + new Parser("if( arg==1 ) int b=2; return b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); + } + } + + @Test + public void testHalfDef2() { + try { + new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'b'",e.getMessage()); + } + } + + @Test + public void testRegress1() { + try { + new Parser("if(arg==2) int a=1; else int b=2; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Cannot define a new name on one arm of an if",e.getMessage()); + } + } + + + @Test + public void testBadNum() { + try { + new Parser("return 1-;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); + } + } + + @Test + public void testKeyword1() { + try { + new Parser("int true=0; return true;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'true'",e.getMessage()); + } + } + + @Test + public void testKeyword2() { + try { + new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'else'",e.getMessage()); + } + } + + @Test + public void testKeyword3() { + try { + new Parser("int a=1; ififif(arg)inta=2;return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'ififif'",e.getMessage()); + } + } + + @Test + public void testBad7() { + try { + new Parser("return 1;}").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, unexpected }",e.getMessage()); + } + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java new file mode 100644 index 00000000..5e823f5d --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -0,0 +1,296 @@ +package com.seaofnodes.simple; + +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 Chapter06Test { + + @Test + public void testPeepholeReturn() { + Parser parser = new Parser( +""" +if( true ) return 2; +return 1; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 2;", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + @Test + public void testPeepholeRotate() { + Parser parser = new Parser( +""" +int a = 1; +if (arg) + a = 2; +return (arg < a) < 3; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((arg<Phi(Region14,2,1))<3);", stop.toString()); + } + + @Test + public void testPeepholeCFG() { + Parser parser = new Parser( +""" +int a=1; +if( true ) + a=2; +else + a=3; +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 2;", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + @Test + public void testIfIf() { + Parser parser = new Parser( +""" +int a=1; +if( arg!=1 ) + a=2; +else + a=3; +int b=4; +if( a==2 ) + b=42; +else + b=5; +return b;""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region39,42,5);", stop.toString()); + } + + @Test + public void testIfArgIf() { + Parser parser = new Parser( +""" +int a=1; +if( 1==1 ) + a=2; +else + a=3; +int b=4; +if( arg==2 ) + b=a; +else + b=5; +return b;""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region32,2,5);", stop.toString()); + } + + @Test + public void testMerge3With2() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( arg==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +#showGraph;""", TypeInteger.constant(2)); + StopNode stop = parser.parse(); + assertEquals("return 5;", stop.toString()); + } + + @Test + public void testMerge3With1() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( arg==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +""", TypeInteger.constant(1)); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 3;", stop.toString()); + } + + @Test + public void testMerge3Peephole() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( 1==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + } + + @Test + public void testMerge3Peephole1() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( 1==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +""", TypeInteger.constant(1)); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 3;", stop.toString()); + } + + @Test + public void testMerge3Peephole3() { + Parser parser = new Parser( +""" +int a=1; +if( arg==1 ) + if( 1==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +""", TypeInteger.constant(3)); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 4;", stop.toString()); + } + + @Test + public void testDemo1NonConst() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +if( arg ) { + a = 2; + if( arg ) { b = 2; } + else b = 3; +} +return a+b; +"""); + StopNode ret = parser.parse().iterate(true); + assertEquals("return Phi(Region26,4,1);", ret.toString()); + } + + + @Test + public void testDemo1True() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +if( arg ) { + a = 2; + if( arg ) { b = 2; } + else b = 3; +} +return a+b; +""", TypeInteger.constant(1)); + StopNode ret = parser.parse().iterate(true); + assertEquals("return 4;", ret.toString()); + } + + @Test + public void testDemo1False() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +if( arg ) { + a = 2; + if( arg ) { b = 2; } + else b = 3; +} +return a+b; +""", TypeInteger.constant(0)); + StopNode ret = parser.parse().iterate(true); + assertEquals("return 1;", ret.toString()); + } + + @Test + public void testDemo2NonConst() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +int c = 0; +if( arg ) { + a = 1; + if( arg==2 ) { c=2; } else { c=3; } + if( arg ) { b = 2; } + else b = 3; +} +return a+b+c; +"""); + StopNode ret = parser.parse().iterate(true); + assertEquals("return (Phi(Region38,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + } + + + @Test + public void testDemo2True() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +int c = 0; +if( arg ) { + a = 1; + if( arg==2 ) { c=2; } else { c=3; } + if( arg ) { b = 2; } + else b = 3; +} +return a+b+c; +""", TypeInteger.constant(1)); + StopNode ret = parser.parse().iterate(true); + assertEquals("return 6;", ret.toString()); + } + + @Test + public void testDemo2arg2() { + Parser parser = new Parser(""" +int a = 0; +int b = 1; +int c = 0; +if( arg ) { + a = 1; + if( arg==2 ) { c=2; } else { c=3; } + if( arg ) { b = 2; } + else b = 3; +} +return a+b+c; +""", TypeInteger.constant(2)); + StopNode ret = parser.parse().iterate(true); + assertEquals("return 5;", ret.toString()); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java new file mode 100644 index 00000000..0685f6f9 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -0,0 +1,243 @@ +package com.seaofnodes.simple; + +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 Chapter07Test { + + + @Test + public void testExample() { + Parser parser = new Parser( + """ + while(arg < 10) { + arg = arg + 1; + #showGraph; + } + return arg; + """); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + } + + @Test + public void testRegression() { + Parser parser = new Parser( +""" +int a = 1; +if(arg){}else{ + while(a < 10) { + a = a + 1; + } +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + } + + @Test + public void testWhileNested() { + Parser parser = new Parser( + +""" +int sum = 0; +int i = 0; +while(i < arg) { + i = i + 1; + int j = 0; + while( j < arg ) { + sum = sum + j; + j = j + 1; + } +} +return sum; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + @Test + public void testWhileScope() { + Parser parser = new Parser( +""" +int a = 1; +int b = 2; +while(a < 10) { + if (a == 2) a = 3; + else b = 4; +} +return b; +"""); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + @Test + public void testWhileNestedIfAndInc() { + Parser parser = new Parser( +""" +int a = 1; +int b = 2; +while(a < 10) { + if (a == 2) a = 3; + else b = 4; + b = b + 1; + a = a + 1; +} +return b; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + + @Test + public void testWhile() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + a = a + 1; + a = a + 2; +} +return a; +"""); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop9,1,((Phi_a+1)+2));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + } + + @Test + public void testWhilePeep() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + a = a + 1; + a = a + 2; +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + @Test + public void testWhile2() { + Parser parser = new Parser( +""" +int a = 1; +while(arg) a = 2; +return a; +"""); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + @Test + public void testWhile2Peep() { + Parser parser = new Parser( +""" +int a = 1; +while(arg) a = 2; +return a; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + System.out.println(IRPrinter.prettyPrint(stop, 99)); + } + + @Test + public void testWhile3() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + int b = a + 1; + a = b + 2; +} +return a; +"""); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop9,1,((Phi_a+1)+2));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + } + + @Test + public void testWhile3Peep() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + int b = a + 1; + a = b + 2; +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + @Test + public void testWhile4() { + Parser parser = new Parser( +""" +int a = 1; +int b = 2; +while(a < 10) { + int b = a + 1; + a = b + 2; +} +return a; +"""); + Node._disablePeephole = true; + StopNode stop = parser.parse(true); + assertEquals("return Phi(Loop10,1,((Phi_a+1)+2));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + Node._disablePeephole = false; + } + + @Test + public void testWhile4Peep() { + Parser parser = new Parser( +""" +int a = 1; +int b = 2; +while(a < 10) { + int b = a + 1; + a = b + 2; +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java new file mode 100644 index 00000000..82b04c60 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -0,0 +1,212 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Assert; +import org.junit.Test; +import org.junit.Ignore; + +import static org.junit.Assert.*; + +public class Chapter08Test { + + @Test + public void testEx6() { + Parser parser = new Parser( + """ +while(arg < 10) { + arg = arg + 1; + if (arg == 5) + break; + if (arg == 6) + break; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region36,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + Assert.assertEquals(5L, Evaluator.evaluate(stop, 1)); + Assert.assertEquals(10L, Evaluator.evaluate(stop, 6)); + } + + + @Test + public void testEx5() { + Parser parser = new Parser( + """ +int a = 1; +while(arg < 10) { + arg = arg + 1; + if (arg == 5) + continue; + if (arg == 7) + continue; + a = a + 1; +} +return a; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop9,1,Phi(Region41,Phi_a,(Phi_a+1)));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + + @Test + public void testEx4() { + Parser parser = new Parser( + """ +while(arg < 10) { + arg = arg + 1; + if (arg == 5) + continue; + if (arg == 6) + break; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region34,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + } + + @Test + public void testEx3() { + Parser parser = new Parser( + """ +while(arg < 10) { + arg = arg + 1; + if (arg == 6) + break; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + } + + + @Test + public void testEx2() { + Parser parser = new Parser( + """ +while(arg < 10) { + arg = arg + 1; + if (arg == 5) + continue; + if (arg == 6) + continue; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + + @Test + public void testEx1() { + Parser parser = new Parser( + """ +while(arg < 10) { + arg = arg + 1; + if (arg == 5) + continue; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode); + } + + @Test + public void testRegress1() { + Parser parser = new Parser( + """ +while( arg < 10 ) { + int a = arg+2; + if( a > 4 ) + break; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return arg;", stop.toString()); + System.out.println(IRPrinter.prettyPrint(stop,99)); + } + + @Test + public void testRegress2() { + Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testBreakOutsideLoop() { + try { + new Parser(""" +if(arg <= 10) { + break; + arg = arg + 1; +} +return arg; +""").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("No active loop for a break or continue",e.getMessage()); + } + } + + @Test + public void testRegress3() { + Parser parser = new Parser(""" +while(arg < 10) { + break; +} +return arg; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return arg;", stop.toString()); + } + + @Test + public void testRegress4() { + Parser parser = new Parser(""" +int a = 1; +while(arg < 10) { + a = a + 1; + if (arg > 2) { + int a = 17; + break; + } +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + } + + @Test + public void testRegress5() { + Parser parser = new Parser(""" +int a = 1; +while(1) { + a = a + 1; + if (a<10) continue; + break; +} +return a; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java new file mode 100644 index 00000000..d29de3e3 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -0,0 +1,302 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.Ignore; + +import static org.junit.Assert.*; + +public class Chapter09Test { + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +int v0=0; +arg=0; +while(v0) { + while(1) if(arg*arg*0==0) {} + while(0) {} + arg=1; +} +return 0; + """); + StopNode stop = parser.parse().iterate(true); + } + + @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(true); + assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); + Assert.assertEquals(2L, Evaluator.evaluate(stop, 1)); + Assert.assertEquals(23L, Evaluator.evaluate(stop, 11)); + } + + @Test + public void testGVN2() { + Parser parser = new Parser( + """ +return arg*arg-arg*arg; + """); + StopNode stop = parser.parse(true); + assertEquals("return 0;", stop.toString()); + Assert.assertEquals(0L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist1() { + Parser parser = new Parser( + """ +int step = 1; +while (arg < 10) { + arg = arg + step + 1; +} +return arg; + """); + StopNode stop = parser.parse(true).iterate(true); + assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist2() { + Parser parser = new Parser( + """ +int cond = 0; +int one = 1; +while (arg < 10) { + if (cond) one = 2; + arg = arg + one*3 + 1; +} +return arg; + """); + StopNode stop = parser.parse(true).iterate(true); + assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testWorklist3() { + Parser parser = new Parser( + """ +int v1 = 0; +int v2 = 0; +int v3 = 0; +int v4 = 0; +int v5 = 0; +int v6 = 0; +int v7 = 0; +int v8 = 0; +while (arg) { + if (v1) v2 = 1; + if (v2) v3 = 1; + if (v3) v4 = 1; + if (v4) v5 = 1; + if (v5) v6 = 1; + if (v6) v7 = 1; + if (v7) v8 = 1; + arg = arg + v8 + 1; +} +return arg; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop16,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(true); + assertEquals("Stop[ return 0; return 0; ]", stop.toString()); + } + + @Test + public void testWhile0() { + Parser parser = new Parser("while(0) continue; if(0) arg=0;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testWhile1() { + Parser parser = new Parser(""" +if(0) while(0) { + int arg=arg; + while(0) {} +} +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + + + @Test + public void testPrecedence() { + Parser parser = new Parser("return 3-1+2;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 4;", stop.toString()); + } + + @Test + public void testSwap2() { + Parser parser = new Parser("return 1+(1+1);"); + StopNode stop = parser.parse().iterate(true); + 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(true); + assertEquals("return Phi(Loop11,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(true); + assertEquals("return 0;", stop.toString()); + } + + + @Test + public void testFuzz2() { + Parser parser = new Parser("return 0+-0;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testFuzz3() { + Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", 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(true); + assertEquals("return 0;", 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(true); + assertEquals("return 0;", 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(true); + assertEquals("return 0;", 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(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testFuzz8() { + Parser parser = new Parser("while(arg) arg = arg - 1; #showGraph; return arg;"); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop8,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/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java new file mode 100644 index 00000000..451d3da2 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -0,0 +1,383 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.StopNode; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter10Test { + + + @Test + public void testStruct() { + Parser parser = new Parser(""" +struct Bar { + int a; + int b; +} +struct Foo { + int x; +} +Foo? foo = null; +Bar bar = new Bar; +bar.a = 1; +bar.a = 2; +return bar.a; +"""); + StopNode stop = parser.parse(false).iterate(true); + System.out.println(IRPrinter.prettyPrint(stop, 99, true)); + assertEquals("return 2;", stop.toString()); + } + + @Test + public void testExample() { + Parser parser = new Parser(""" +struct Vector2D { int x; int y; } +Vector2D v = new Vector2D; +v.x = 1; +if (arg) + v.y = 2; +else + v.y = 3; +return v; +"""); + StopNode stop = parser.parse(true).iterate(true); + System.out.println(IRPrinter.prettyPrint(stop, 99, true)); + assertEquals("return new Vector2D;", stop.toString()); + } + + @Test + public void testBug() { + Parser parser = new Parser(""" +struct s0 { + int v0; +} +s0? v1=null; +int v3=v1.zAicm; +"""); + try { parser.parse(true); fail(); } + catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); } + } + + @Test + public void testBug2() { + Parser parser = new Parser(""" +struct s0 { int v0; } +arg=0+new s0.0; +"""); + try { parser.parse(true); fail(); } + catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + } + + @Test + public void testLoop() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = new Bar; +while (arg) { + bar.a = bar.a + 2; + arg = arg + 1; +} +return bar.a; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString()); + } + + @Test + public void testIf() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = new Bar; +if (arg) bar = null; +bar.a = 1; +return bar.a; +"""); + try { parser.parse(true).iterate(true); fail(); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); } + } + + @Test + public void testIf2() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar? bar = null; +if (arg) bar = new Bar; +bar.a = 1; +return bar.a; +"""); + try { parser.parse(true).iterate(true); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); } + } + + @Test + public void testIf3() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar bar = null; +if (arg) bar = null; +bar.a = 1; +return bar.a; +"""); + try { parser.parse(true); fail(); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); } + } + + @Test + public void testIfOrNull() { + Parser parser = new Parser(""" +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +if( bar ) bar.a = 1; +return bar; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return Phi(Region16,null,new Bar);", stop.toString()); + } + + @Test + public void testIfOrNull2() { + Parser parser = new Parser( +""" +struct Bar { int a; } +Bar? bar = new Bar; +if (arg) bar = null; +int rez = 3; +if( !bar ) rez=4; +else bar.a = 1; +return rez; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return Phi(Region33,4,3);", stop.toString()); + } + + @Test + public void testWhileWithNullInside() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0? v0 = new s0; +int ret = 0; +while(arg) { + ret = v0.v0; + v0 = null; + arg = arg - 1; +} +return ret; +"""); + try { parser.parse(true).iterate(true); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); } + } + + @Test + public void testRedeclareStruct() { + Parser parser = new Parser(""" +struct s0 { + int v0; +} +s0? v1=new s0; +s0? v1; +v1=new s0; +"""); + try { parser.parse(true); fail(); } + catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); } + } + + @Test + public void testIter() { + // Build and use an iterator + Parser parser = new Parser( +""" +struct Iter { + int x; + int len; +} +Iter i = new Iter; +i.len = arg; +int sum=0; +while( i.x < i.len ) { + sum = sum + i.x; + i.x = i.x + 1; +} +return sum; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return Phi(Loop15,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + } + + + + + @Test + public void test1() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +while(arg) { + s0 v0 = new s0; + v0.v0 = arg; + arg = arg-1; + if (arg==5) ret=v0; + #showGraph; +} +return ret; +"""); + StopNode stop = parser.parse(true).iterate(true); + System.out.println(IRPrinter.prettyPrint(stop, 99, true)); + assertEquals("return Phi(Loop11,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString()); + } + + @Test + public void test2() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +s0 v0 = new s0; +while(arg) { + v0.v0 = arg; + arg = arg-1; + if (arg==5) ret=v0; + #showGraph; +} +return ret; +"""); + StopNode stop = parser.parse(true).iterate(true); + System.out.println(IRPrinter.prettyPrint(stop, 99, true)); + assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); + } + + + @Test + public void test3() { + Parser parser = new Parser(""" +struct s0 {int v0;} +s0 ret = new s0; +while(arg < 10) { + s0 v0 = new s0; + if (arg == 5) ret=v0; + arg = arg + 1; +} +return ret; +"""); + StopNode stop = parser.parse(true).iterate(true); + System.out.println(IRPrinter.prettyPrint(stop, 99, true)); + assertEquals("return Phi(Loop11,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString()); + } + + @Test + public void testBug3() { + Parser parser = new Parser(""" +struct s0 { + int f0; +} +if(0>=0) return new s0; +return new s0; +int v0=null.f0; +"""); + try { parser.parse(true); fail(); } + catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); } + } + + @Test + public void testBug4() { + Parser parser = new Parser(""" +if(0) { + while(0) if(arg) continue; + int v0=0; + while(1) { + int arg=-arg; + v0=arg; + } +} + """); + StopNode stop = parser.parse(true).iterate(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testBug5() { + Parser parser = new Parser(""" +struct s0 { + int f0; +} +if(0) return 0; +else return new s0; +if(new s0.f0) return 0; + """); + StopNode stop = parser.parse(true).iterate(true); + assertEquals("return new s0;", stop.toString()); + } + + @Test + public void testBug6MissedWorklist() { + Parser parser = new Parser(""" +while(0) {} +int v4=0; +while(0<arg) { + v4=v4+1; + while(1) v4=-v4; + while(0) arg=-1; +} +return 0; + """); + StopNode stop = parser.parse().iterate(true); + } + + @Test + public void testBug7() { + Parser parser = new Parser(""" +struct s0 { int f0; } +s0 v0 = new s0; +while(v0.f0) {} +s0 v1 = v0; +return v1; + """); + StopNode stop = parser.parse().iterate(true); + assertEquals("return new s0;", stop.toString()); + } + + + @Test + public void testBug8() { + Parser parser = new Parser(""" +int v2=0; +while(0) +while(0) {} +{ + { + { + int v36=0; + { + while(0) { + { + while(-v2) { + { + while(v36) { + while(v2) return 0; + break; + } } + if(-v2) break; + } + } + } + } + } } +} +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testBug9() { + Parser parser = new Parser(""" +int v0=arg==0; +while(v0) continue; +return 0; +"""); + StopNode stop = parser.parse().iterate(true); + assertEquals("return 0;", stop.toString()); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java new file mode 100644 index 00000000..9b05d66c --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -0,0 +1,514 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Test; + + +import static org.junit.Assert.assertEquals; + +public class Chapter11Test { + + // A placeholder test used to rapidly rotate through fuzzer produced issues + @Test + public void testFuzzer() { + Parser parser = new Parser( +""" +while(-----arg) { + while(-arg) { + while(-----arg*---0) { + int N=--false==false<--arg; + while(N<-4==N) { + N=N+N; + } + } + { + int kzEl_r7EYz=3*----0!=-arg<(37*--97!=0/arg==arg)<=-(--arg!=0); + while(kzEl_r7EYz<---1) { + kzEl_r7EYz=kzEl_r7EYz+kzEl_r7EYz*arg>=24; + if(7) continue; + kzEl_r7EYz=false; + if(-kzEl_r7EYz+arg<-3) continue; + else { + while(kzEl_r7EYz) continue; + arg=----3; + kzEl_r7EYz=-(-----arg*1>-kzEl_r7EYz); + int v2uF_4ba=9; + } + } + } + int pjcp7rv=arg; + { + int m2ya3=--arg-pjcp7rv; + while(m2ya3<-----pjcp7rv>=pjcp7rv+--pjcp7rv!=---pjcp7rv!=-arg<---(-m2ya3)>m2ya3<=-----true>14!=--m2ya3) { + m2ya3=m2ya3+-(--0<=false<pjcp7rv/arg<=-true); + while((--m2ya3)<--arg<-----arg*false!=arg<pjcp7rv*arg) { + pjcp7rv=-3+-arg; + break; + } + while(-3) { + while(67+0-61) m2ya3=m2ya3; + if(----pjcp7rv==---(---arg)) pjcp7rv=0!=-23; + else pjcp7rv=----89; + while(m2ya3!=m2ya3<pjcp7rv) m2ya3=1; + while(--arg<=pjcp7rv) pjcp7rv=-32<=-m2ya3+62; + } + break; + } + } + { + int arg=-1<-9!=(-arg>=-pjcp7rv>=arg<=arg); + while(arg<-arg) { + arg=arg+--arg; + { + while((--93)--pjcp7rv*-pjcp7rv+arg) arg=0-(6>=(--(---41/(0))!=--pjcp7rv)>=---2*33); + pjcp7rv=11; + int c=pjcp7rv; + if(--107) break; + else break; + } + } + } + pjcp7rv=pjcp7rv; + int aR0Wkua=-pjcp7rv==pjcp7rv; + { + if(-0!=-0) while(-true) break; + else { + while(aR0Wkua) pjcp7rv=-0/pjcp7rv; + pjcp7rv=pjcp7rv; + } + } + } + if(-arg) { + { + int Isdlt0fr91=0!=arg; + while(Isdlt0fr91<-Isdlt0fr91==-Isdlt0fr91+true) { + Isdlt0fr91=Isdlt0fr91+-1!=true; + if(-arg) if(--arg<=Isdlt0fr91) Isdlt0fr91=-Isdlt0fr91; + int i=---arg<---arg!=--40; + { + int jVX43Ln4Dm=-(6)==-Isdlt0fr91/----0; + while(jVX43Ln4Dm<--(3-3)) { + jVX43Ln4Dm=jVX43Ln4Dm+-0<25; + i=24<--(jVX43Ln4Dm); + } + } + i=(arg!=----false>=7); + int Te=5>-arg; + break; + } + } + arg=arg!=--true/arg; + { + int UX=arg; + while(UX<-3*arg) { + UX=UX+arg/-1; + arg=-3--(-UX--47<arg==--arg==arg)/arg--UX>=-arg; + if(---12) while(-UX) break; + break; + } + } + continue; + } + if(arg) arg=--0; + else if(1) continue; + int DtTlhjYq=19==arg; + { + arg=--DtTlhjYq<=(DtTlhjYq/---DtTlhjYq<=-DtTlhjYq); + if(---arg) continue; + else { + int Zsl=40; + while(Zsl<--Zsl--false>((Zsl))==Zsl!=--DtTlhjYq-DtTlhjYq-DtTlhjYq) { + Zsl=Zsl+arg; + } + } + if((arg)) if(-DtTlhjYq) { + } + else while(((2)==3*arg/DtTlhjYq)<=0==---arg) return false; + else { + int lwbF=arg>=0; + while(lwbF<57) { + lwbF=lwbF+--(-arg+lwbF)>=-2>=arg!=arg; + arg=----0+0; + while(49) break; + if(true) DtTlhjYq=-----arg; + DtTlhjYq=0!=-----(0); + } + } + arg=---arg!=DtTlhjYq/--0; + DtTlhjYq=-DtTlhjYq/(--arg<arg); + if(32) { + int q=-DtTlhjYq; + while(q<arg*(0*q)/0) { + q=q+DtTlhjYq; + DtTlhjYq=arg; + } + } + else if(--arg) if(15) break; + else arg=0==arg<=-55>DtTlhjYq; + else { + } + DtTlhjYq=-0; + { + while((0)<-arg) DtTlhjYq=-DtTlhjYq; + } + if(30/--(arg)>=0) DtTlhjYq=-0>=arg; + else { + int Y2wcIQJ=--DtTlhjYq*---11; + if(arg==arg) { + Y2wcIQJ=-----Y2wcIQJ; + Y2wcIQJ=Y2wcIQJ<7; + if(DtTlhjYq) arg=arg; + else break; + while(-arg!=-31==(false<=-15>=-DtTlhjYq+--6*-----Y2wcIQJ<=---arg!=-Y2wcIQJ<=---0<-arg*--Y2wcIQJ)) DtTlhjYq=arg!=3; + } + continue; + } + arg=DtTlhjYq; + arg=--3/(---arg)>=80>=-DtTlhjYq<=DtTlhjYq; + arg=51+---(-true)!=arg!=----1==-DtTlhjYq; + if(11<=arg) if((arg)+79<32+(-arg)) continue; + else if(true==DtTlhjYq) break; + break; + } +} +if(arg!=62==arg) { + while(---arg) { + { + int bhmDacPM=arg/arg; + while(bhmDacPM<arg) { + bhmDacPM=bhmDacPM+80*((-bhmDacPM)/---bhmDacPM>1)<bhmDacPM-2!=7; + arg=-0; + arg=bhmDacPM>=-arg+-arg; + break; + } + } + int arg=true; + arg=arg<=arg; + { + int KlC=30; + while(KlC<-2) { + KlC=KlC+-1!=25<=-KlC>KlC>----0; + int Ij=arg>=-arg<=---10; + { + int LAbgurVa2R=-arg!=Ij; + while(LAbgurVa2R<-true) { + LAbgurVa2R=LAbgurVa2R+Ij; + KlC=-LAbgurVa2R; + Ij=42*(-Ij>true); + while(Ij) arg=KlC; + KlC=-----7>-(--KlC<--7)==-LAbgurVa2R; + } + } + int a=Ij<=-KlC; + if(Ij) { + int r1W=a!=-false; + } + else { + arg=56>=Ij; + while(54) break; + arg=8>=-(a<--Ij)/a>=-a-KlC; + } + int Yt_T=KlC; + arg=KlC; + } + } + if(----false/--arg/12+-----0) if(-9+-arg) while(arg!=(arg)) arg=--1>=-71-arg!=---(--arg*11+102)/--arg; + else if(----arg) continue; + if(-arg-arg) { + arg=-(-arg)*--(-3)<(-7==-arg----arg); + { + int arg=arg; + while(arg<-0>arg==-arg-(-arg>1+11)) { + arg=arg+6-arg!=--0>---30>=---arg<=arg/(3)+---arg; + } + } + arg=-arg+arg; + arg=--47+-arg<1>arg*(-arg==-arg--0>3); + arg=(----arg)!=arg+--59; + arg=--4+arg==-(arg)-arg; + } + else { + while(--true-----true*-25+-18>arg>=-----(-arg)---13) { + arg=-0+--true<false; + continue; + } + arg=true<=arg; + break; + } + arg=arg*22<--(0+-arg); + { + continue; + } + } + arg=1*---((3<=true)==---2); + arg=7+-(-1); + return arg+--true; +} +else { + int o3ypEsewxM=-(---arg); + int vzXB=(-(---11<=--o3ypEsewxM>=--0))<=-0/-arg; + int L=-arg; + int B4mHcrf=---vzXB; + int oZWGdzZl=-o3ypEsewxM; + int K6tQqa1=11; + while(0!=--L) { + int sdZfTM=vzXB*true+K6tQqa1; + while(sdZfTM<false) { + sdZfTM=sdZfTM+arg; + sdZfTM=oZWGdzZl; + K6tQqa1=-K6tQqa1; + arg=---K6tQqa1; + while((3+-K6tQqa1)<oZWGdzZl) { + if(-----(-1)) sdZfTM=-false; + {} + } + K6tQqa1=vzXB!=-(B4mHcrf+-7>false); + sdZfTM=vzXB; + } + } + L=false; + while(31) B4mHcrf=arg; + oZWGdzZl=-0<--oZWGdzZl<=--2; + if(K6tQqa1>=20<=46) { + if(-1+-L) if(oZWGdzZl) { + int wmhv=-----0; + } + int arg=0; + o3ypEsewxM=---(oZWGdzZl); + return -oZWGdzZl/0; + } + else if(arg) { + int yKk5=B4mHcrf>=4>K6tQqa1<-B4mHcrf/o3ypEsewxM; + return -K6tQqa1; + } + else { + if(---0) { + {} + {} + } + return o3ypEsewxM; + } +} +"""); + StopNode stop = parser.parse(false).iterate(false); + assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); + } + + + @Test + public void testPrimes() { + Parser parser = new Parser( +""" +if( arg < 2 ) return 0; +int primeCount = 1; +int prime = 3; +while( prime <= arg ) { + int isPrime = 1; + // Check for even case, so the next loop need only check odds + if( (prime/2)*2 == prime ) + continue; + // Check odds up to sqrt of prime + int j = 3; + while( j*j <= prime ) { + if( (prime/j)*j == prime ) { + isPrime = 0; + break; + } + j = j + 2; + } + if( isPrime ) + primeCount = primeCount + 1; + prime = prime + 2; +} +return primeCount; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("Stop[ return 0; return Phi(Loop19,1,Phi(Region81,Phi_primeCount,Phi(Region76,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below + assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 + assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 + assertEquals(2L, Evaluator.evaluate(stop, 4)); // 2, 3 + assertEquals(3L, Evaluator.evaluate(stop, 5)); // 2, 3, 5 + assertEquals(4L, Evaluator.evaluate(stop, 10)); // 2, 3, 5, 7 + } + + @Test + public void testAntiDeps1() { + Parser parser = new Parser( +""" +struct S { int f; } +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return .f;", stop.toString()); + } + + @Test + public void testAntiDeps2() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +int i = 0; +if (arg) { + if (arg+1) v = t; + i = v.f; +} else { + v.f = 2; +} +return i; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return Phi(Region31,.f,0);", stop.toString()); + } + + @Test + public void testAntiDeps3() { + Parser parser = new Parser( +""" +struct S { int f; } +S v0 = new S; +S? v1; +if (arg) v1 = new S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return new S;", stop.toString()); + } + + + @Test + public void testAntiDeps4() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +if (arg+1) arg= 0; +while (arg) v.f = 2; +return i; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return .f;", stop.toString()); + } + + @Test + public void testAntiDeps5() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +while(1) { + while(arg+1) { arg=arg-1; } + if (arg) break; + v.f = 2; +} +return v; +"""); + StopNode stop = parser.parse(false).iterate(true); + assertEquals("return new S;", stop.toString()); + } + + @Test + public void testAntiDeps6() { + Parser parser = new Parser( +""" +struct s { int v; } +s ptr=new s; +while( -arg ) + ptr = new s; +while(1) + arg = arg+ptr.v; +"""); + StopNode stop = parser.parse(false).iterate(false); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testAntiDeps7() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +int i = v.f; +while (arg) { + v.f = arg; + arg = arg-1; +} +return i; +"""); + StopNode stop = parser.parse(false).iterate(false); + assertEquals("return .f;", stop.toString()); + } + + @Test + public void testAntiDeps8() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +while(arg) { + arg=arg-1; + int f = v.f; + v.f = 2; + if (arg) arg = f; + else v.f = 3; +} +return arg; +"""); + StopNode stop = parser.parse(false).iterate(false); + assertEquals("return Phi(Loop13,arg,Phi(Region35,.f,0));", stop.toString()); + } + + @Test + public void testAntiDeps9() { + Parser parser = new Parser( +""" +struct S { int f; } +S v = new S; +S t = new S; +if (arg) { + v.f=2; + int i=t.f; + v.f=i; +} +return v; +"""); + StopNode stop = parser.parse(false).iterate(false); + assertEquals("return new S;", stop.toString()); + } + + @Test + public void testExample2() { + Parser parser = new Parser( + """ +struct S { int f; } +S v = new S; +int i = arg; +while (arg > 0) { + int j = i/3; + if (arg == 5) + v.f = j; + arg = arg - 1; +} +return v; + """); + StopNode stop = parser.parse(false).iterate(true); + //assertEquals("return new S;", stop.toString()); + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java new file mode 100644 index 00000000..699711fc --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.fuzzer.Fuzzer; +import org.junit.Test; +import org.junit.Ignore; + + +import static org.junit.Assert.assertTrue; + +/** + * To use the fuzzer run the <code>fuzzPeeps</code> methods. + * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line + * via <code>mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps</code> + */ +public class FuzzerWrap { + + @Ignore + @Test + public void fuzzPeeps() { + var fuzzer = new Fuzzer(); + for (int i=0; i<1000000; i++) + fuzzer.fuzzPeeps(i); + assertTrue(fuzzer.noExceptions()); + } + + + @Test + @Ignore + public void fuzzPeepTiming() { + var fuzzer = new Fuzzer(); + int max_nid=0; + for (int i=0; i<1000000; i++) + max_nid = fuzzer.fuzzPeepTiming(i, max_nid); + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java new file mode 100644 index 00000000..e5961815 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -0,0 +1,74 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SchedulerTest { + + private static void assertObj(Object obj, String name, Object... fields) { + assertEquals(Evaluator.Obj.class, obj.getClass()); + var o = (Evaluator.Obj)obj; + assertEquals(name, o.struct()._name); + Assert.assertArrayEquals(fields, o.fields()); + } + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode._disablePeephole = true; + StopNode stop = parser.parse(true);//.iterate(true); + var eval = new Evaluator(stop); + assertEquals(2L, eval.evaluate(1, 10)); + } + + @Test + public void testStoreInIf() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v0=new S; +if(arg) v0.f=1; +return v0; +"""); + StopNode stop = parser.parse(false).iterate(true); + var eval = new Evaluator(stop); + assertObj(eval.evaluate(0, 10), "S", 0L); + assertObj(eval.evaluate(1, 10), "S", 1L); + } + + @Test + public void testStoreInIf2() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode stop = parser.parse(false).iterate(true); + var eval = new Evaluator(stop); + assertEquals(2L, eval.evaluate(0, 10)); + assertEquals(2L, eval.evaluate(1, 10)); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter11/src/test/java/com/seaofnodes/simple/TypeTest.java new file mode 100644 index 00000000..f18fb306 --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -0,0 +1,145 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.type.*; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +public class TypeTest { + + // Test basic properties and GLB + @Test + public void testTypeAdHoc() { + TypeStruct s1 = TypeStruct.make("s1", new Field[]{ + Field.make("s1", TypeInteger.BOT, "a"), + Field.make("s1", TypeInteger.BOT, "b") }); + TypeStruct s2 = TypeStruct.make("s2", new Field[]{ + Field.make("s2", TypeInteger.BOT, "a"), + Field.make("s2", TypeInteger.BOT, "b") }); + Assert.assertEquals(s1, s1.glb()); + Assert.assertNotEquals(s1, s1.dual()); + Assert.assertEquals(s1, s1.dual().glb()); + + TypeMem m1 = TypeMem.make(s1.get("a")._alias); + TypeMem m2 = TypeMem.make(s1.get("b")._alias); + TypeMem m3 = TypeMem.make(s2.get("a")._alias); + TypeMem m4 = TypeMem.make(s2.get("b")._alias); + + Assert.assertNotEquals(m1, m2); + Assert.assertNotEquals(m2, m3); + Assert.assertNotEquals(m3, m4); + + Assert.assertEquals(TypeStruct.BOT, s1.meet(s2)); + Assert.assertEquals(TypeMem .BOT, m1.meet(m2)); + Assert.assertEquals(TypeMem .BOT, m2.meet(m3)); + Assert.assertEquals(TypeMem .BOT, m3.meet(m4)); + + Assert.assertEquals(TypeMem.BOT, m1.glb()); + Assert.assertEquals(m1, m1.dual()); + Assert.assertEquals(TypeMem.TOP, m1.glb().dual()); + + TypeMemPtr ptr1 = TypeMemPtr.make(s1); + Assert.assertEquals(s1, ptr1._obj); + TypeMemPtr ptr2 = TypeMemPtr.make(s2); + Assert.assertEquals(s2, ptr2._obj); + + TypeMemPtr ptr1nil = TypeMemPtr.make(s1, true); + Assert.assertEquals(s1, ptr1nil._obj); + Assert.assertTrue(ptr1nil._nil); + Assert.assertFalse(ptr1nil._obj==null); + TypeMemPtr ptr2nil = TypeMemPtr.make(s2, true); + Assert.assertEquals(s2, ptr2nil._obj); + + Assert.assertNotEquals(ptr1, ptr2); + Assert.assertNotEquals(ptr1, ptr1.glb()); + Assert.assertEquals(ptr1nil, ptr1.glb()); + + Assert.assertEquals(ptr1, ptr1.dual().dual()); + Assert.assertEquals(ptr1.glb(), ptr1.dual().glb()); + Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil)); + Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR)); + + TypeMemPtr TOP = TypeMemPtr.TOP; + TypeMemPtr BOT = TypeMemPtr.BOT; + TypeMemPtr PTR = TypeMemPtr.VOIDPTR; + TypeMemPtr NULL = TypeMemPtr.NULLPTR; + Type PTR_meet_NULL = NULL.meet(PTR); + Assert.assertEquals(BOT, PTR_meet_NULL); + Type ptr1_meet_ptr2 = ptr1.meet(ptr2); + Assert.assertEquals(PTR, ptr1_meet_ptr2); + Type NULL_join_ptr1 = NULL.join(ptr1); + Assert.assertEquals(TOP, NULL_join_ptr1); + Type NULL_join_PTR = PTR.join(NULL); + Assert.assertEquals(TOP, NULL_join_PTR); + Type ptr1_dual = ptr1.dual(); + Type nullableptr1_dual = ptr1nil.dual(); + } + + // Test theoretical properties. + // This is a symmetric complete bounded (ranked) lattice. + // Also the meet is commutative and associative. + // The lattice has a dual (symmetric), and join is ~(~x meet ~y). + // See https://en.wikipedia.org/wiki/Lattice_(order). + @Test + public void testLatticeTheory() { + Type[] ts = Type.gather(); + + // Confirm commutative & complete + for( Type t0 : ts ) + for( Type t1 : ts ) { + check_commute (t0,t1); + check_symmetric(t0,t1); + } + + // Confirm associative + for( Type t0 : ts ) + for( Type t1 : ts ) + for( Type t2 : ts ) + assoc(t0,t1,t2); + + // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C) + for( Type t0 : ts ) + for( Type t1 : ts ) + if( t0.isa(t1) ) + for( Type t2 : ts ) { + Type t02 = t0.join(t2); + Type t12 = t1.join(t2); + Type mt = t02.meet(t12); + assertSame(mt,t12); + } + } + + // By design in meet, args are already flipped to order _type, which forces + // symmetry for things with badly ordered _type fields. The question is + // still interesting for other orders. + private static void check_commute( Type t0, Type t1 ) { + if( t0==t1 ) return; + if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order + Type mta = t0.meet(t1); + Type mtb = t1.meet(t0); // Reverse args and try again + assertSame(mta,mtb); + } + + // A & B = MT + // Expect: ~A & ~MT == ~A + // Expect: ~B & ~MT == ~B + private static void check_symmetric( Type t0, Type t1 ) { + if( t1==t0 ) return; + Type mt = t0.meet(t1); + Type ta = mt.dual().meet(t1.dual()); + Type tb = mt.dual().meet(t0.dual()); + assertSame(ta,t1.dual()); + assertSame(tb,t0.dual()); + } + + private static void assoc( Type t0, Type t1, Type t2 ) { + Type t01 = t0 .meet(t1 ); + Type t12 = t1 .meet(t2 ); + Type t01_2 = t01.meet(t2 ); + Type t0_12 = t0 .meet(t12); + assertSame(t01_2,t0_12); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java new file mode 100644 index 00000000..2447a36a --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -0,0 +1,230 @@ +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.TypeStruct; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Objects; + +public class Evaluator { + + private static final Object MEMORY = new Object(); + + public record Obj(TypeStruct struct, Object[] fields) { + @Override + public String toString() { + var sb = new StringBuilder(); + sb.append("Obj<").append(struct._name).append("> {"); + for(int i=0; i<struct._fields.length; i++) { + sb.append("\n ").append(struct._fields[i]._fname).append("=").append(fields[i]); + } + return sb.append("\n}").toString(); + } + } + + private static final MethodHandle get_MemOpNode_field; + + static { + try { + var MemOpNode = Class.forName("com.seaofnodes.simple.node.MemOpNode"); + var field = MemOpNode.getDeclaredField("_field"); + field.setAccessible(true); + get_MemOpNode_field = MethodHandles.lookup().unreflectGetter(field); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + private static int getFieldIndex(TypeStruct struct, Node memop) { + Field field; + try { + field = (Field)get_MemOpNode_field.invoke(memop); + } catch (Throwable e) { + throw new RuntimeException(e); + } + for(int i=0;i<struct._fields.length;i++) { + if (struct._fields[i]._alias == field._alias) return i; + } + throw new AssertionError("Field "+field._fname+" of struct " +field._sname + " not found in struct " + struct._name); + } + + public enum Status { + TIMEOUT, + FALLTHROUGH + } + + /** + * Find the start node from some node in the graph or null if there is no start node + */ + private static StartNode findStart(BitSet visit, Node node) { + if (node == null) return null; + if( visit.get(node._nid) ) return null; + visit.set(node._nid); + StartNode start = node instanceof StartNode ? (StartNode) node : null; + for( Node def : node._inputs ) { + var res = findStart(visit,def); + if( res != null ) start = res; + } + for( Node use : node._outputs ) { + var res = findStart(visit,use); + if( res != null ) start = res; + } + return start; + } + + private final Object[] values; + private final ArrayList<Object> phiCache = new ArrayList<>(); + private final StartNode start; + private final Scheduler.Block startBlock; + + public Evaluator(Node graph) { + var visited = new BitSet(); + this.start = findStart(visited, graph); + if (this.start == null) { + this.values = null; + this.startBlock = null; + return; + } + this.startBlock = Scheduler.schedule(this.start); + this.values = new Object[visited.length()]; + } + + private long div(DivNode div) { + long in2 = vall(div.in(2)); + return in2 == 0 ? 0 : vall(div.in(1)) / in2; + } + + private Object alloc(NewNode alloc) { + var type = ((TypeMemPtr)alloc.compute())._obj; + return new Obj(type, new Object[type._fields.length]); + } + + private Object load(LoadNode load) { + var from = (Obj)val(load.in(2)); + var idx = getFieldIndex(from.struct, load); + return from.fields[idx]; + } + + private Object store(StoreNode store) { + var to = (Obj)val(store.in(2)); + var val = val(store.in(3)); + var idx = getFieldIndex(to.struct, store); + to.fields[idx] = val; + return null; + } + + private static boolean isTrue(Object obj) { + if (obj == null) return false; + if (obj instanceof Number n) return n.longValue() != 0; + return true; + } + + private Object val(Node node) { + return values[node._nid]; + } + + private long vall(Node node) { + var v = val(node); + if (v instanceof Number n) return n.longValue(); + throw new AssertionError("Not a long " + v); + } + + private Object cons(ConstantNode cons) { + var type = cons.compute(); + if (type instanceof TypeInteger i) return i.value(); + assert type instanceof TypeMemPtr; + return null; + } + + private Object exec(Node node) { + return switch (node) { + case ConstantNode cons -> cons(cons); + case AddNode add -> vall(add.in(1)) + vall(add.in(2)); + case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; + case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; + case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; + case DivNode div -> div(div); + case MinusNode minus -> -vall(minus.in(1)); + case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); + case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; + case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); + case CastNode cast -> val(cast.in(1)); + case LoadNode load -> load(load); + case StoreNode store -> store(store); + case NewNode alloc -> alloc(alloc); + case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; + case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; + default -> throw new AssertionError("Unexpected node " + node); + }; + } + + /** + * Run the graph until either a return is found or the number of loop iterations are done. + */ + public Object evaluate(long parameter, int loops) { + if (start == null) return Status.TIMEOUT; + var s = new Object[start.compute()._types.length]; + s[1] = parameter; + for(int i=2;i<s.length;i++) s[i] = MEMORY; + values[start._nid] = s; + int i=0; + Scheduler.Block block = this.startBlock; + for(;;) { + for(; i<block.nodes().length;i++) values[block.nodes()[i]._nid] = exec(block.nodes()[i]); + i=0; + switch (block.exit()) { + case null: + return Status.FALLTHROUGH; + case ReturnNode ret: + return val(ret.in(1)); + case IfNode ifn: + block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; + if (block == null) return Status.FALLTHROUGH; + break; + case RegionNode region: + if (loops-- <= 0) return Status.TIMEOUT; + int exit = block.exitId(); + assert exit > 0 && region.nIns() > exit; + block = block.next()[0]; + assert block != null; + for (; i < block.nodes().length; i++) { + if (!(block.nodes()[i] instanceof PhiNode)) break; + phiCache.add(val(block.nodes()[i].in(exit))); + } + for (i=0; i<phiCache.size(); i++) { + values[block.nodes()[i]._nid] = phiCache.get(i); + } + phiCache.clear(); + break; + default: + throw Utils.TODO("Unexpected control node " + block.exit()); + } + } + } + + public static Object evaluate(StopNode graph) { + return evaluate(graph, 0); + } + + public static Object evaluate(StopNode graph, long parameter) { + return evaluate(graph, parameter, 1000); + } + + public static Object evaluate(StopNode graph, long parameter, int loops) { + var res = evaluateWithResult(graph, parameter, loops); + if (res == Status.TIMEOUT) throw new RuntimeException("Timeout"); + return res; + } + + public static Object evaluateWithResult(StopNode graph, long parameter, int loops) { + return new Evaluator(graph).evaluate(parameter, loops); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java new file mode 100644 index 00000000..8b86d67a --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -0,0 +1,594 @@ +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +import java.util.*; + +/** + * A late scheduler for test cases. + * <br> + * <br> + * This scheduler is used for tests and is not the final scheduler. + * <br> + * <br> + * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. + * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy + * this scheduler might place initialization code in loops. + * <br> + * We schedule late here since memory edges have some implicit information. There can only be one + * memory edge (for a field) at a time. However, before <code>if</code> statements this might split. + * When scheduling early a store might be moved out of the loop but then there are two edges alive. + * The one through the store node and the adjacent one. This is an invalid schedule and instead of + * handling this case a late scheduler does not have this problem as memory edges are combined + * through phi nodes. + * <br> + * <br> + * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. + * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes + * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. + */ +public class Scheduler { + + /** + * A basic block with a schedule of the containing nodes. + * + * @param nodes The nodes in the final schedule of this block + * @param exit The exit node of this block. + * @param exitId In case the next block is a region this gives the entry id into the region. + * @param next The next blocks. + */ + public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} + + /** + * Definition of a block in the building process. + */ + private static class BasicBlock { + + /** + * Dominator of this block + */ + final BasicBlock dom; + /** + * Id of this block where <code>a.depth < b.depth</code> means that a is before b or + * both are in different branches. + */ + final int depth; + /** + * Previous blocks + */ + final BasicBlock[] prev; + /** + * Entry to this block + */ + final Node entry; + /** + * Schedules node in reverse order. + */ + final ArrayList<Node> reverseSchedule = new ArrayList<>(); + + /** + * Initialize the block. + * @param entry The node that started this block + * @param depth The depth of this node. All previous nodes need to have a lower value. + * @param prev The previous blocks. + */ + BasicBlock(Node entry, int depth, BasicBlock... prev) { + BasicBlock dom = null; + int max = -1; + for(var bb: prev) { + max = Math.max(max, bb.depth); + dom = dom==null?bb:dom(dom, bb); + } + assert depth > max; + this.dom = dom; + this.depth = depth; + this.entry = entry; + this.prev = prev; + } + + } + + /** + * Ancillary data for nodes used during the scheduling process. + */ + private static class NodeData { + + /** + * The node this ancillary data is used for. + */ + final Node node; + + /** + * Number of alive users not yet scheduled. + */ + int users = 1; + + /** + * Block into which the node should be placed so far. + * This will be updated with every placed use. + */ + BasicBlock block; + + /** + * Initialize ancillary data for a node + * @param node The node this ancillary data is associated with. + */ + NodeData(Node node) { + this.node = node; + } + + } + + /** + * Ancillary data for nodes. + */ + private final IdentityHashMap<Node, NodeData> data = new IdentityHashMap<>(); + /** + * List of nodes which can be placed since all users are placed. + */ + private final Stack<NodeData> scheduleQueue = new Stack<>(); + + /** + * Get the ancillary data for an alive node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node. + */ + NodeData d(Node node) { + return od(node).orElseThrow(AssertionError::new); + } + + /** + * Get the ancillary data for a potentially dead node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node an alive node or + * <code>Optional.empty()</code> for <code>null</code> or dead nodes. + */ + Optional<NodeData> od(Node node) { + return Optional.ofNullable(data.get(node)); + } + + /** + * Checks a node for validity + * @param data The node to check. + * @return true if all placed inputs are before this node. + */ + private boolean isValid(NodeData data) { + return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d); + } + + /** + * Checks that node is not XCtrl. + * @param node The node to check. + * @return true if the node is not XCtrl. + */ + private static boolean isNotXCtrl(Node node) { + return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL) && !(node instanceof XCtrlNode); + } + + /** + * Return the dominator of <code>a</code> and <code>b</code> + * @param a Block a + * @param b Block b + * @return The dominator of <code>a</code> and <code>b</code> + */ + private static BasicBlock dom(BasicBlock a, BasicBlock b) { + while (a!=b) { + if (a.depth >= b.depth) a=a.dom; + if (b.depth > a.depth) b=b.dom; + } + return a; + } + + /** + * Return if a is before b. + * @param a Block a + * @param b Block b + * @return true is a is before b. + */ + private static boolean isBefore(BasicBlock a, BasicBlock b) { + while (b.depth > a.depth) { + while (b.dom.depth > a.depth) b = b.dom; + for (int i=0; i<b.prev.length-1; i++) { + if (isBefore(a, b.prev[i])) return true; + } + b = b.prev[b.prev.length-1]; + } + return a == b; + } + + /** + * Is node placed during the control flow graph build. + * @param data Node to check. + * @return true if node is placed during the control flow graph build. + */ + private static boolean isPinnedNode(NodeData data) { + return data.node.isCFG() || data.node instanceof PhiNode; + } + + /** + * Refine placement of a node. + * @param data The node to refine. + * @param block The block before the node should be scheduled. + */ + private void refinePlacement(NodeData data, BasicBlock block) { + assert !isPinnedNode(data); + data.block = data.block == null ? block : dom(data.block, block); + assert isValid(data); + } + + /** + * Refine placement if before is happening before data + * @param data The node for which the placement should be refined. + * @param before The node before which the data node should be placed. + */ + private void optionalRefinePlacement(NodeData data, Node before) { + od(before).ifPresent(d->{ + // before might be in a different branch. So check this case with isBefore + // and only refine the placement if it is not in a different branch. + if (isBefore(d.block, data.block)) refinePlacement(data, d.block); + }); + } + + /** + * Schedule all nodes not yet scheduled. + */ + private void doSchedule() { + while (!scheduleQueue.isEmpty()) { + var data = scheduleQueue.pop(); + assert !isPinnedNode(data); + + assert data.block != null; + assert data.users == 0; + + if (data.node instanceof LoadNode l) { + assert isValid(data); + // Handle anti-deps of load nodes. + // At this point all anti-dep nodes are scheduled, + // but they did not refine the placement + // so do that now. + var mem = l.in(1); + for(var out : mem._outputs) { + if (out instanceof PhiNode p) { + var r = p.in(0); + for (int i = 1; i < p.nIns(); i++) { + if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); + } + } else if (!(out instanceof LoadNode)) { + optionalRefinePlacement(data, out); + } + } + } + + assert isValid(data); + data.block.reverseSchedule.add(data.node); + + for(var in:data.node._inputs) { + if (in!=null) update(d(in), data.block); + } + if (data.node instanceof StoreNode s) { + // Store nodes have anti-deps to load nodes. + // So decrease the uses of these loads when the store is placed. + for (var out: s.in(1)._outputs) { + if (out instanceof LoadNode) od(out).ifPresent(this::decUsers); + } + } + } + + // Now all nodes should be placed and have a block assigned + assert this.data.values().stream().noneMatch(d->d.block==null); + } + + /** + * Decrement the not-yet-placed users of a block. + * @param data The node for which the users should be decremented. + */ + private void decUsers(NodeData data) { + assert data.users > 0; + if (--data.users == 0) { + assert data.block != null; + assert isValid(data); + + // When all users are gone this node can be scheduled. + scheduleQueue.push(data); + } + } + + /** + * Update the placement of a node + * @param data The node to update + * @param block The block before which the node should happen. + */ + private void update(NodeData data, BasicBlock block) { + assert block != null; + if (data.users == 0) { + assert isPinnedNode(data); + return; + } + refinePlacement(data, block); + decUsers(data); + } + + /** + * Checks if a CFG node is ready to be placed. This is used for regions joining branches. + * @param node The CFG node to check + * @return true if all parents of a CFG node are placed. + */ + private boolean isCFGNodeReady(Node node) { + if (node instanceof LoopNode l) { + assert d(l.in(1)).block != null; + } else if (node instanceof RegionNode r) { + for(int i=1; i<r.nIns(); i++) { + if (od(r.in(i)).map(d->d.block==null).orElse(false)) return false; + } + } else if (node instanceof XCtrlNode) { + return false; + } else { + assert d(node.in(0)).block != null; + } + return true; + } + + /** + * Schedule all phi nodes. + * @param phiQueue List of all the phi nodes to schedule. + */ + private void schedulePhis(Stack<NodeData> phiQueue) { + while(!phiQueue.empty()) { + var data = phiQueue.pop(); + var phi = (PhiNode) data.node; + var r = phi.in(0); + data.block = d(r).block; + for(int i=1; i<phi.nIns(); i++) { + var in = phi.in(i); + od(r.in(i)).ifPresent(d->update(d(in), d.block)); + } + } + } + + /** + * Build the control flow graph. + * @param start The start node. + */ + private void doBuildCTF(StartNode start) { + var queue = new Stack<NodeData>(); + var phiQueue = new Stack<NodeData>(); + queue.push(d(start)); + int depth = 0; + while (!queue.isEmpty()) { + var data = queue.pop(); + var node = data.node; + assert node.isCFG(); + BasicBlock block; + switch (node) { + case StartNode s: + block = new BasicBlock(s, depth++); + break; + case LoopNode l: + block = new BasicBlock(l, depth++, d(l.in(1)).block); + break; + case RegionNode r: + var prev = new ArrayList<BasicBlock>(); + for(int i=1; i<r.nIns(); i++) { + od(r.in(i)).ifPresent(d->prev.add(d.block)); + } + block = new BasicBlock(r, depth++, prev.toArray(BasicBlock[]::new)); + break; + case IfNode i: + block = d(i.in(0)).block; + break; + case ReturnNode r: + block = d(r.in(0)).block; + break; + default: + block = new BasicBlock(node, depth++, d(node.in(0)).block); + } + data.block = block; + if (node instanceof RegionNode r) { + // Regions might have phis which need to be scheduled. + // Put them on a list for later scheduling. + for (var out:r._outputs) { + if (out instanceof PhiNode p) { + var d = this.data.get(p); + if (d!=null) { + d.users=0; + phiQueue.push(d); + } + } + } + } + if (!(node instanceof ReturnNode)) + for (Node n:node._outputs) if (n!=null && n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); + var b = block; + for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); + } + + // Schedule all phi nodes. + schedulePhis(phiQueue); + } + + /** + * Mark a node alive and create ancillary data for it. + * @param queue List into which this node should be placed if it needs to be visited. + * @param node Node which should be marked alive. + * @param cfg If this node is a CFG node. + */ + private void markAlive(Stack<NodeData> queue, Node node, boolean cfg) { + var nd = od(node).orElse(null); + if (nd != null) { + // Node was already visited, just increase the users. + if (nd.users>0) nd.users++; + return; + } + assert node.isCFG() == cfg; + assert isNotXCtrl(node); + nd = new NodeData(node); + if (cfg) nd.users=0; + data.put(node, nd); + queue.push(nd); + } + + /** + * Mark all alive nodes. + * First all CFG nodes are marked, then all other nodes. + * @param node THe start node. + */ + private void doMarkAlive(Node node) { + var cfgQueue = new Stack<NodeData>(); + var dataQueue = new Stack<NodeData>(); + var mem = new Stack<NodeData>(); + markAlive(cfgQueue, node, true); + // Mark all CFG nodes. + while (!cfgQueue.isEmpty()) { + var data = cfgQueue.pop(); + node = data.node; + assert node.isCFG(); + if (!(node instanceof ReturnNode)) { + for (var out : node._outputs) if (out!=null && out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); + } + for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); + } + // Mark all other nodes. + while (!dataQueue.isEmpty()) { + var data = dataQueue.pop(); + node = data.node; + assert !node.isCFG(); + if (node instanceof PhiNode phi) { + var r = phi.in(0); + for (int i=1; i<phi.nIns(); i++) { + if (od(r.in(i)).isPresent()) markAlive(dataQueue, phi.in(i), false); + } + } else { + for (var in : node._inputs) if (in != null && !in.isCFG()) markAlive(dataQueue, in, false); + } + if (node instanceof StoreNode) mem.push(data); + } + // Handle store nodes and increase load with an anti-dep to the store. + while (!mem.isEmpty()) { + var data = mem.pop(); + node = data.node; + for(var out:node.in(1)._outputs) { + if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + } + } + } + + /** + * Helper function to append the nodes of a block to an array of nodes + * @param arr The array the nodes of a block should be appended to. + * @param node A potentially separator node + * @param block The block from which the nodes should be appended. + * @return The combined new array. + */ + private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { + var idx = arr.length; + arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); + if (node != null) arr[idx++] = node; + for(int i=block.reverseSchedule.size()-1;i>=0;i--) { + arr[idx++] = block.reverseSchedule.get(i); + } + return arr; + } + + /** + * Find the CFG output of a node + * @param node The node + * @return THe CFG output of the node. + */ + private static Node findSingleCFGOut(Node node) { + if (node instanceof StartNode) { + for(var n:node._outputs) if (n instanceof CProjNode p && p._idx==0) return p; + return null; + } + assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; + for(var n:node._outputs) if(n.isCFG()) return n; + return null; + } + + /** + * Build the final data structure after all the scheduling happened. + * @param start The start node + * @return The final schedule. + */ + private Block build(Node start) { + var blocks = new IdentityHashMap<Node, Block>(); + var queue = new Stack<NodeData>(); + queue.push(d(start)); + + // Visit all CFG nodes and create blocks for them. + // This can combine blocks. + while(!queue.isEmpty()) { + var data = queue.pop(); + var first = data.node; + Node prev; + var last = first; + Node[] arr = new Node[0]; + if (first instanceof RegionNode) { + var nodes = new ArrayList<Node>(); + for(var out: first._outputs) { + if (out instanceof PhiNode) { + if(od(out).isPresent()) nodes.add(out); + } + } + arr = nodes.toArray(arr); + } + arr = appendNodes(arr, null, data.block); + assert !(first instanceof IfNode); + for(;;) { + prev = last; + last = findSingleCFGOut(last); + if (last == null) break; + if (last instanceof RegionNode) { + if (blocks.get(last) == null && last != first) + queue.push(d(last)); + break; + } + if (last instanceof ReturnNode) break; + if (last instanceof IfNode if_) { + for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out)); + break; + } + data = d(last); + arr = appendNodes(arr, data.node, data.block); + } + blocks.put(first, switch(last) { + case null -> new Block(arr, null, -1, new Block[0]); + case IfNode i -> new Block(arr, i, -1, new Block[2]); + case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); + case ReturnNode r -> new Block(arr, r, -1, new Block[0]); + default -> throw new AssertionError("Unexpected block exit node"); + }); + } + // Update the next pointer of all blocks. + for (var block: blocks.values()) { + switch(block.exit) { + case null: break; + case IfNode i: + for(var out:i._outputs) if (out instanceof CProjNode p) block.next[p._idx] = blocks.get(p); + break; + case RegionNode r: + block.next[0] = blocks.get(r); + break; + default: + assert block.exit instanceof ReturnNode; + } + } + // And return the block for the start node. + return blocks.get(start); + } + + /** + * Create a schedule for the program reachable from start. + * @param start The start node + * @return The final schedule. + */ + public static Scheduler.Block schedule(StartNode start) { + var scheduler = new Scheduler(); + scheduler.doMarkAlive(start); + scheduler.doBuildCTF(start); + scheduler.doSchedule(); + return scheduler.build(start); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java new file mode 100644 index 00000000..29a6853c --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -0,0 +1,185 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.Random; +import java.util.function.Consumer; + +/** + * Implementation of the fuzzer. + * Random testcases are generated using the ScriptGenerator. + * This will generate a script similar to how the parser parses it but instead of parsing it will + * randomly choose a variation the parser would parse and generate it. + * These scripts are the parsed by the parser and all exceptions are caught and filtered + * to only show one occurrence of one problem. This includes script which generate different + * results when the compiled graph with and without peeps is executed. + * To aid debugging scripts that cause errors are then reduced by applying rules and checking + * that the same issue persists. + */ +public class Fuzzer { + + private static final int EVAL_TIMEOUT = 1000; + + /** + * List of exceptions already encountered. + * This is used to filter new exceptions and discard them if they are already found. + */ + private final ArrayList<Throwable> exceptions = new ArrayList<>(); + + /** + * Filter and record an exception in a script. + * @param e The exception caused by the script + * @param script The script causing the exception + * @param reproducer Reproducer which should be used to reproduce the test case. + */ + private void recordException(Throwable e, String script, Consumer<String> reproducer, long seed) { + for (var ex : exceptions) { + if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return; + } + exceptions.add(e); + System.out.println("========== Stack =========="); + e.printStackTrace(System.out); + System.out.println("========== Seed =========="); + System.out.println(seed); + System.out.println("========== Code ==========="); + System.out.println(script); + System.out.println("========= Reduced ========="); + System.out.println(Reducer.reduce(script, e, reproducer)); + System.out.println("==========================="); + System.out.flush(); + } + + private static boolean neq(Object a, Object b) { + if (Objects.equals(a, b)) return false; + if (a instanceof Evaluator.Obj ea && b instanceof Evaluator.Obj eb) { + if (ea.struct() != eb.struct()) return true; + for(int i=0;i<ea.fields().length;i++) { + if (neq(ea.fields()[i], eb.fields()[i])) return true; + } + return false; + } + return true; + } + + /** + * Check that two graphs result in the same output when supplied with a value + * @param e1 Evaluator for the first graph + * @param e2 Evaluator for the second graph + * @param in The input value to both graphs to test for an equal output + */ + private static void checkGraphs(Evaluator e1, Evaluator e2, long in) { + var r1 = e1.evaluate(in, EVAL_TIMEOUT); + var r2 = e2.evaluate(in, EVAL_TIMEOUT); + if (r1 == Evaluator.Status.TIMEOUT || r2 == Evaluator.Status.TIMEOUT) return; + if (neq(r1, r2)) + throw new RuntimeException("Different calculations values " + r1 + " vs " + r2); + } + + /** + * Run checks for script. Compile the script with peeps enabled and disabled. + * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid. + * If the script was successfully parsed check that both version behave the same. + * @param script The script to test + * @param valid If the script is definitely valid. If not some exceptions may be suppressed. + */ + private static void runCheck(String script, boolean valid) { + StopNode stop1; + try { + stop1 = FuzzerUtils.parse(script, false); + } catch (RuntimeException e1) { + try { + FuzzerUtils.parse(script, true); + } catch (RuntimeException e2) { + if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { + if (!valid || e1.getClass() == RuntimeException.class) return; + } else { + e1.addSuppressed(e2); + } + } + throw e1; + } + var stop2 = FuzzerUtils.parse(script, true); + var e1 = new Evaluator(stop1); + var e2 = new Evaluator(stop2); + checkGraphs(e1, e2, 0); + checkGraphs(e1, e2, 1); + checkGraphs(e1, e2, 10); + } + + /** + * Check that the script does not result in exceptions. + * @param script The script to test. + * @param valid If the script is valid or if it might contain syntax errors. + */ + private void check(String script, boolean valid, long seed) { + try { + runCheck(script, valid); + } catch (Throwable e) { + recordException(e, script, s->runCheck(s, valid), seed); + } + } + + /** + * Run one test with the given seed, and check for peephole counts. + * @param seed The seed to use for generating this test case + */ + public int fuzzPeepTiming(long seed, int max_nids) { + var rand = new Random(seed); + var sb = new StringBuilder(); + new ScriptGenerator(rand, sb, true).genProgram(); + try { + var parser = new Parser(sb.toString()); + + var stop = parser.parse(); + int parse_peeps= Node.ITER_CNT; + int parse_nops = Node.ITER_NOP_CNT; + double parse_nop_ratio = (double)parse_nops/parse_peeps; + int nids = Node.UID(); + if( nids <= max_nids ) return max_nids; + + double parse_peeps_per_node = (double)parse_peeps/nids; + + stop.iterate(); + int iter_peeps= Node.ITER_CNT; + int iter_nops = Node.ITER_NOP_CNT; + double iter_nop_ratio = (double)iter_nops/iter_peeps; + double iter_peeps_per_node = (double)iter_peeps/nids; + + System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n", + seed, + parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node, + iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node); + return nids; + } catch (Throwable e) { + // ignore exceptions, let the normal fullPeeps catch and reduce, + // we're interested in valid program optimizations + } + return max_nids; + } + + + /** + * Run one test with the given seed. + * @param seed The seed to use for generating this test case + */ + public void fuzzPeeps(long seed) { + var rand = new Random(seed); + var sb = new StringBuilder(); + var valid = new ScriptGenerator(rand, sb, true).genProgram(); + check(sb.toString(), valid, seed); + } + + + /** + * Check that no exceptions happened. + * @return true if no exception happened + */ + public boolean noExceptions() { + return exceptions.isEmpty(); + } +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java new file mode 100644 index 00000000..18ef069a --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -0,0 +1,140 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.BitSet; + +/** + * Some utilities for the fuzzer. + */ +class FuzzerUtils { + + /** + * Get the val of a private field + */ + @SuppressWarnings("unchecked") + public static <T> 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 the messages are similar. + */ + private static boolean similarMessages(String a, String b) { + if (a == null || b == null) return true; + a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); + b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized"); + return a.equals(b); + } + + /** + * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization + * and allow the other to be any stack trace. + */ + private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) { + if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing. + return a[0].equals(b[0]); + } + + /** + * Checks if two exceptions are caused by the same problem. + */ + public static boolean isExceptionFromSameCause(Throwable a, Throwable b) { + if (a == null || b == null) return a == b; // Handle the null case + if (a.getClass() != b.getClass()) return false; // Different classes + if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard + var s1 = a.getStackTrace(); + var s2 = b.getStackTrace(); + if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins + if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes + var o1 = a.getSuppressed(); + var o2 = b.getSuppressed(); + if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression + for (int i=0; i<o1.length; i++) { + if (!isExceptionFromSameCause(o1[i], o2[i])) return false; // Different suppression + } + return true; + } + + /** + * Parse script with peepholes enabled or disabled + */ + public static StopNode parse(String script, boolean runPeeps) { + var err = System.err; + var out = System.out; + try { + System.setErr(NULL_PRINT_STREAM); + System.setOut(NULL_PRINT_STREAM); + try { + set_MID_ASSERT.invokeExact(false); + } catch (Throwable e) { + throw rethrow(e); + } + var parser = new Parser(script); + Node._disablePeephole = !runPeeps; + var stop = parser.parse(); + return runPeeps ? stop.iterate() : stop; + } finally { + NodeWalkVisit.clear(); + System.setErr(err); + System.setOut(out); + } + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java new file mode 100644 index 00000000..3731b40c --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java @@ -0,0 +1,176 @@ +package com.seaofnodes.simple.fuzzer; + +import java.util.Arrays; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is used to reduce test cases created by the fuzzer. + * It applies rewrite rules to the test case and checks that the + * test still produces the same exception. + * This is done in a fixpoint iteration until every rewrite produces + * no or a different exception. + */ +public class Reducer { + + private interface Replacer extends BiConsumer<StringBuilder, Matcher> {} + + /** + * Holder for the rewrite rules + */ + private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) { + Replace(Pattern p, Object... replacements) { + this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new)); + } + private static Replacer from(Object o) { + if (o instanceof Replacer r) return r; + if (o instanceof Integer i) return (s, m)->s.append(m.group(i)); + if (o instanceof String r) return (s, m)->s.append(r); + throw new IllegalArgumentException(); + } + } + + /** + * List of rewrite rules. + */ + private static final Replace[] REPLACEMENTS = { + new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"), + new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""), + new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""), + new Replace(Pattern.compile("\\b--"), "+"), + new Replace(Pattern.compile("-"), ""), + new Replace(Pattern.compile("\\b\\+-"), "-"), + new Replace(Pattern.compile("(?<![+\\-*/<>=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"), + new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1), + new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2), + new Replace(Pattern.compile("\\(([^()]+)\\)"), 1), + new Replace(Pattern.compile("\\d+\\+"), ""), + new Replace(Pattern.compile("\\+\\d+"), ""), + new Replace(Pattern.compile("\\n\\n+"), "\n"), + new Replace(Pattern.compile("^\\n+|\\n+$"), ""), + new Replace(Pattern.compile("else *"), ""), + new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""), + new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "") + }; + + /** + * Predicate to test if the reduced script generates the same error. + */ + private final Predicate<String> tester; + + /** + * Create a reducer with a test function. + * @param tester Test function which returns true when the same issue is found in the script. + */ + private Reducer(Predicate<String> tester) { + this.tester = tester; + } + + /** + * Rewrites the script with one rewrite rule. + */ + private String doReplacement(String script, Pattern pattern, Replacer[] replacements) { + var matcher = pattern.matcher(script); + var sb = new StringBuilder(); + var last = 0; + var skip = 0; + outer: + while (matcher.find(last + skip)) { + sb.append(script, last, matcher.start()); + last = matcher.end(); + skip = last == matcher.start() ? 1 : 0; + var pos = sb.length(); + for (var replacement : replacements) { + replacement.accept(sb, matcher); + var tail = sb.length(); + sb.append(script, last, script.length()); + if (tester.test(sb.toString())) { + sb.setLength(tail); + continue outer; + } + sb.setLength(pos); + } + last = matcher.start(); + skip = 1; + } + sb.append(script, last, script.length()); + return sb.toString(); + } + + /** + * Clean up variable names from the random ones generated by the fuzzer. + */ + private String cleanVariables(String script) { + var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) ([0-9a-zA-Z_]+)").matcher(script); + int[] num = {0, 0}; + while (matcher.find()) { + var type = matcher.group(1); + var var = matcher.group(2); + if (type.equals("new")) continue; + String prefix = "v"; + int t = 0; + if (type.equals("struct")) { + t = 1; + prefix = "s"; + } + if (var.matches(prefix+"\\d+")) continue; + while (script.contains(prefix+num[t])) num[t]++; + var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]); + if (tester.test(n)) { + script = n; + } + } + return script; + } + + /** + * Run all rewrite rules once. + */ + private String doAllReplacements(String script) { + for (var replace: REPLACEMENTS) { + String old; + do { + old = script; + script = doReplacement(script, replace.pattern, replace.replacements); + } while (replace.iterate && !old.equals(script)); + } + return script; + } + + /** + * Reduce the script to a minimal version which produces the same problem with a test function. + * @param tester Test function which returns true when the same issue is found in the script. + */ + public static String reduce(String script, Predicate<String> tester) { + var reducer = new Reducer(tester); + String old; + do { + old = script; + script = reducer.doAllReplacements(script); + script = reducer.cleanVariables(script); + } while (!script.equals(old)); + return script; + } + + /** + * Reduce the script to a minimal version which produces the same problem with a reproducer. + * @param ex The exception originally caused by the script + * @param reproducer Test function which throws an exception which will be checked against ex. + */ + public static String reduce(String script, Throwable ex, Consumer<String> reproducer) { + return reduce(script, s->{ + Throwable nex = null; + try { + reproducer.accept(s); + } catch (Throwable e) { + nex = e; + } + return FuzzerUtils.isExceptionFromSameCause(nex, ex); + }); + } + +} diff --git a/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java new file mode 100644 index 00000000..155063ab --- /dev/null +++ b/chapter11/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -0,0 +1,792 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.ScopeNode; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.function.Predicate; + +/** + * Generate a pseudo random script. + * This generator will generate the same script for two random number generators initialized with the same seed. + * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions. + * It is guaranteed to terminate. + */ +public class ScriptGenerator { + + private static final int MAX_STATEMENTS_PER_BLOCK = 5; + private static final int MAX_BINARY_EXPRESSIONS_PER_EXPRESSION = 10; + private static final int MAX_UNARY_EXPRESSIONS_PER_EXPRESSION = 6; + private static final int MAX_NAME_LENGTH = 10; + private static final int MAX_BLOCK_DEPTH = 4; + private static final int MAX_EXPRESSION_DEPTH = 10; + + /** + * Number of spaces per indentation + */ + private static final int INDENTATION = 4; + /** + * Valid characters for identifiers. The last 10 are not valid for the first character. + */ + private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; + /** + * List of keywords not valid for identifiers. + */ + private static final HashSet<String> KEYWORDS; + + static { + try { + KEYWORDS = FuzzerUtils.getFieldValue(new Parser(""), "KEYWORDS"); + } catch (Throwable e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Flag for a statement that it does not pass on control flow to the next statement. + */ + private static final int FLAG_STOP = 0x01; + /** + * Flag for a statement that it contains a final if statement without an else branch. + */ + private static final int FLAG_IF_WITHOUT_ELSE = 0x02; + /** + * Binary operators. + */ + private static final String[] BINARY_OP = {"==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"}; + /** + * Unary operators. + */ + private static final String[] UNARY_OP = {"-"}; + + private static class Type { + final String name; + Type(String name) {this.name=name;} + boolean isa(Type other) { return this == other; } + } + + private static class TypeStruct extends Type { + record Field (String name, Type type) {} + final Field[] fields; + final TypeNullable nullable = new TypeNullable(this); + TypeStruct(String name, Field[] fields) { + super(name); + this.fields = fields; + } + boolean isa(Type other) { return this == other || other == nullable; } + } + + private static class TypeNullable extends Type { + + final Type base; + + TypeNullable(Type base) { + super(base.name+"?"); + this.base = base; + } + + } + + private static final Type TYPE_INT = new Type("int"); + + + private static class Variable { + final String name; + final Type declared; + final Variable shadowing; + Type type; + boolean shadowed = false; + + Variable(String name, Type type, Variable var) { + this.name = name; + this.declared = type; + this.type = type; + this.shadowing = var; + } + } + + /** + * The random number generator used for random decisions. + */ + private final Random random; + /** + * Indentation depth. + */ + private int indentation = 0; + /** + * Output string builder + */ + private final StringBuilder sb; + /** + * Number of nested loops + */ + private int loopDepth = 0; + /** + * Current scope start in the variables list. + */ + private int currScopeStart = 0; + /** + * The depth of blocks allowed. + */ + private int depth = MAX_BLOCK_DEPTH; + /** + * The depth of expressions allowed. + */ + private int exprDepth = MAX_EXPRESSION_DEPTH; + /** + * Current variables in scope. + */ + private final ArrayList<Variable> variables = new ArrayList<>(); + /** + * All defined structs. + */ + private final ArrayList<TypeStruct> structs = new ArrayList<>(); + /** + * Allow to declare structs. + */ + private boolean allowStructs = true; + /** + * If this needs to generate valid scripts or is also allowed to generate invalid ones. + */ + private boolean generateValid; + /** + * If this script might be invalid. + */ + private boolean maybeInvalid = false; + + + /** + * + * @param random the random number generator. + * @param sb the script will be written to this string builder. + * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs. + */ + public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) { + this.random = random; + this.sb = sb; + this.generateValid = generateValid; + } + + /** + * Random number with log distribution + * @param max The maximum value + * @return A random number in the range [0, max) with higher numbers being more uncommon. + */ + private int randLog(int max) { + max--; + int n = 1<<max; + var num = random.nextInt(n); + for (int i=max-1; i>=0; i--) { + if ((num & (1<<i))!=0) return max-i-1; + } + return max; + } + + /** + * Helper function to print indentation. + * @return sb for chaining + */ + private StringBuilder printIndentation() { + return sb.repeat(' ', indentation); + } + + /** + * Generate a random name for variables. This might be invalid when it is a keyword. + * @return The random name generated + */ + private StringBuilder getRandomName() { + int len = random.nextInt(MAX_NAME_LENGTH) + 1; + StringBuilder sb = new StringBuilder(len); + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()-10))); + for (int i=1; i<len; i++) + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + return sb; + } + + /** + * Should invalid code be generated? + * @return True if invalid code should be generated + */ + private boolean generateInvalid() { + if (generateValid || random.nextInt(1000)>2) return false; + maybeInvalid = true; + return true; + } + + /** + * Lookup a variable by name + * @param name name of the variable + * @return The index of the variable in variables or -1 + */ + private int lookup(String name) { + for (int i=variables.size()-1; i>=0; i--) { + if (variables.get(i).name.equals(name)) return i; + } + return -1; + } + + /** + * Lookup a variable by name + * @param name name of the variable + * @return THe variable of null if not found + */ + private Variable lookupVar(String name) { + int idx = lookup(name); + return idx == -1 ? null : variables.get(idx); + } + + /** + * Add a variable to the current scope + * @param name The name of the variable + * @param type The declared type of the variable + */ + private void addVariable(String name, Type type) { + var old = lookupVar(name); + variables.add(new Variable(name, type, old)); + if (old != null) old.shadowed = true; + } + + /** + * Pop variables to level to + * @param to Level until which variables should be popped + */ + private void popVariables(int to) { + while (variables.size() > to) { + var v = variables.removeLast(); + if (v.shadowing != null) v.shadowing.shadowed = false; + } + } + + /** + * Get a random visible variable patching pred + * @param pred The predicate the variable needs to match + * @return A random visible variable matching pred or null if no variable mached pred. + */ + private Variable findVisibleVariablesMatching(Predicate<Variable> pred) { + int num=0; + for (var v:variables) { + if (!v.shadowed && pred.test(v)) num++; + } + if (num == 0) return null; + num = random.nextInt(num); + for (var v:variables) { + if (!v.shadowed && pred.test(v) && num-- == 0) return v; + } + throw new AssertionError(); + } + + /** + * Get a structure by name + * @param name Name of the structure + * @return The structure of null + */ + private TypeStruct getStruct(String name) { + for (var s:structs) { + if (s.name.equals(name)) return s; + } + return null; + } + + /** + * Generate a variable name. This might be new random name or a name from an outer scope. + * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope. + * @return The variable name. + */ + private String getVarName() { + if (currScopeStart > 0 && random.nextInt(10) > 7) { + // Use a variable outside the current scope. + var v = variables.get(random.nextInt(currScopeStart)); + if (lookup(v.name) < currScopeStart) return v.name; + if (!generateValid) { + maybeInvalid = true; + return v.name; + } + } + // Generate a new random variable name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + /** + * Get a random structure name + * @return A random structure name + */ + private String getStructName() { + if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name; + // Generate a new random struct name + StringBuilder sb = getRandomName(); + var v = sb.toString(); + if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) { + maybeInvalid = true; + return v; + } + while (KEYWORDS.contains(v) || getStruct(v) != null) { + sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()))); + v = sb.toString(); + } + return v; + } + + /** + * Get a random type + * @return A random type + */ + private Type getType() { + if (structs.isEmpty() || random.nextBoolean()) return TYPE_INT; + var idx = random.nextInt(structs.size()); + TypeStruct struct = structs.get(idx); + if (random.nextBoolean()) return struct.nullable; + return struct; + } + + /** + * Generates a program and writes it into the string builder supplied in the constructor. + * @return If the program is allowed to be invalid returns if something potentially invalid was generated. + */ + public boolean genProgram() { + var oldGenerateValid = generateValid; + if (!generateValid && random.nextBoolean()) generateValid = true; + this.maybeInvalid = false; + addVariable(ScopeNode.ARG0, TYPE_INT); + currScopeStart = variables.size(); + if ((genStatements() & FLAG_STOP) == 0) { + if (generateValid || random.nextInt(10)<7) { + genReturn(); + } else { + this.maybeInvalid = true; + } + } + generateValid = oldGenerateValid; + return !this.maybeInvalid; + } + + /** + * Generate a list of statements. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated + */ + public int genStatements() { + var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1)); + for (int i=0; i<num; i++) { + printIndentation(); + var stop = genStatement(); + sb.append("\n"); + if ((stop & FLAG_STOP) != 0) return stop; + } + return 0; + } + + /** + * Generate a single statement + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genStatement() { + return switch (random.nextInt(12)) { + case 0 -> allowStructs || generateInvalid() ? genStruct() : genAssignment(); + case 1 -> genBlock(); + case 2 -> genIf(); + case 3 -> genCountedLoop(); + case 4 -> genWhile(); + case 5, 6 -> genDecl(); + case 7, 8, 9 -> genAssignment(); + case 10 -> genNullCheck(); + default -> genExit(); + }; + } + + /** + * Generate a new structure + * @return 0 + */ + public int genStruct() { + var name = getStructName(); + var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)]; + sb.append("struct ").append(name).append(" {\n"); + indentation += INDENTATION; + for (int i=0; i<fields.length; i++) { + var fieldName = getRandomName(); + var type = generateInvalid() ? getType() : TYPE_INT; + printIndentation().append(type.name).append(" ").append(fieldName).append(";\n"); + fields[i] = new TypeStruct.Field(fieldName.toString(), type); + } + indentation -= INDENTATION; + printIndentation().append("}"); + structs.add(new TypeStruct(name, fields)); + return 0; + } + + /** + * Generate a statement for if, else and while blocks. + * This is special since it disallows declarations and prefers to generate blocks. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genStatementBlock() { + var oldAllowStructs = allowStructs; + allowStructs = false; + indentation += INDENTATION; + int res; + if (depth == 0) { + res = random.nextBoolean() ? genAssignment() : genExit(); + } else { + depth--; + res = switch (random.nextInt(12)) { + case 1, 2, 3, 4, 5 -> { + depth++; + indentation -= INDENTATION; + var ret = genBlock(); + indentation += INDENTATION; + depth--; + yield ret; + } + case 6 -> genIf(); + case 7 -> genCountedLoop(); + case 8 -> genWhile(); + case 9 -> genAssignment(); + case 10 -> genNullCheck(); + default -> genExit(); + }; + depth++; + } + indentation -= INDENTATION; + allowStructs = oldAllowStructs; + return res; + } + + /** + * Generate an exit. This can be return or break and continue when in a loop. + * @return FLAG_STOP + */ + public int genExit() { + if (loopDepth == 0 && generateValid) return genReturn(); + return switch (random.nextInt(7)) { + case 0,1,2 -> { + if (loopDepth == 0) maybeInvalid = true; + sb.append("continue;"); + yield FLAG_STOP; + } + case 3,4,5 -> { + if (loopDepth == 0) maybeInvalid = true; + sb.append("break;"); + yield FLAG_STOP; + } + default -> genReturn(); + }; + } + + /** + * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator. + * @return flag FLAG_STOP for the generated statement + */ + public int genBlock() { + if (depth == 0) { + sb.append("{}"); + return 0; + } + var oldAllowStructs = allowStructs; + var oldCSS = currScopeStart; + allowStructs = false; + currScopeStart = variables.size(); + depth--; + sb.append("{\n"); + indentation += INDENTATION; + var stop = genStatements(); + indentation -= INDENTATION; + printIndentation().append("}"); + popVariables(currScopeStart); + depth++; + currScopeStart = oldCSS; + allowStructs = oldAllowStructs; + return stop & ~FLAG_IF_WITHOUT_ELSE; + } + + /** + * Generate an if statement with an optional else block. + * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement + */ + public int genIf() { + sb.append("if("); + genExpression(TYPE_INT); + sb.append(") "); + var stop = genStatementBlock(); + if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { + sb.append("\n"); + printIndentation().append("else "); + stop &= genStatementBlock(); + } else { + stop = FLAG_IF_WITHOUT_ELSE; + } + return stop; + } + + /** + * Generate a block with a null check + * if (nullable) { handle_nullable_as_non_nullable; } + */ + public int genNullCheck() { + var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable); + if (v == null) return genIf(); + var n = (TypeNullable)v.declared; + boolean negate = random.nextBoolean(); + sb.append("if("); + if (negate) sb.append("!"); + sb.append(v.name).append(")"); + if (!negate) v.type = n.base; + var stop = genStatementBlock(); + v.type = n; + if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) { + sb.append("\n"); + printIndentation().append("else "); + if (negate) v.type = n.base; + stop &= genStatementBlock(); + v.type = n; + } else { + stop = FLAG_IF_WITHOUT_ELSE; + } + return stop; + } + + /** + * Generate a while loop. + * @return 0 + */ + public int genWhile() { + sb.append("while("); + genExpression(TYPE_INT); + sb.append(") "); + loopDepth++; + genStatementBlock(); + loopDepth--; + return 0; + } + + /** + * Generate a counted loop of the form + * <code> + * { + * int varname = start_expr; + * while (varname < end_expr) loop_body; + * } + * </code> + * @return 0 + */ + public int genCountedLoop() { + if (depth == 0) return genWhile(); + var oldCSS = currScopeStart; + currScopeStart = variables.size(); + sb.append("{\n"); + indentation += INDENTATION; + String name = getVarName(); + printIndentation().append("int ").append(name).append("="); + genExpression(TYPE_INT); + sb.append(";\n"); + addVariable(name, TYPE_INT); + printIndentation().append("while(").append(name).append("<"); + genExpression(TYPE_INT); + sb.append(") {\n"); + indentation += INDENTATION; + depth--; + loopDepth++; + printIndentation().append(name).append("=").append(name).append("+"); + genExpression(TYPE_INT); + sb.append(";\n"); + genStatements(); + loopDepth--; + depth++; + indentation -= INDENTATION; + printIndentation().append("}\n"); + indentation -= INDENTATION; + printIndentation().append("}"); + popVariables(currScopeStart); + currScopeStart = oldCSS; + return 0; + } + + /** + * Generate a declaration statement. + * @return 0 + */ + public int genDecl() { + var type = getType(); + var name = getVarName(); + sb.append(generateInvalid() ? getRandomName() : type.name).append(" ").append(name); + if (!(type instanceof TypeNullable) || random.nextBoolean()) { + sb.append("="); + genExpression(type); + } + sb.append(";"); + addVariable(name, type); + return 0; + } + + /** + * Generate an assignment statement. + * @return 0 + */ + public int genAssignment() { + Type type; + Type declared; + if (generateInvalid()) { + sb.append(getRandomName()); + type = getType(); + declared = type; + } else { + var v = findVisibleVariablesMatching(va->true); + if (v == null) return genDecl(); + sb.append(v.name); + type = v.type; + declared = v.declared; + } + if (generateInvalid()) { + var name = getRandomName(); + sb.append(".").append(name); + declared = getType(); + } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) { + var field = s.fields[random.nextInt(s.fields.length)]; + sb.append(".").append(field.name); + declared = field.type; + } + sb.append("="); + genExpression(declared); + sb.append(";"); + return 0; + } + + /** + * Generate a return statement. + * @return FLAG_STOP + */ + public int genReturn() { + var type = getType(); + sb.append("return "); + genExpression(type); + sb.append(";"); + return FLAG_STOP; + } + + /** + * Generate a binary expression. + * This method does not care about operator precedence. + */ + public void genExpression(Type type) { + if (generateInvalid()) type = getType(); + if (type != TYPE_INT) { + genUnary(type); + return; + } + var num = randLog(MAX_BINARY_EXPRESSIONS_PER_EXPRESSION); + while(num-->0) { + genUnary(TYPE_INT); + sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]); + } + genUnary(TYPE_INT); + } + + /** + * Generate a unary expression. + */ + public void genUnary(Type type) { + if (generateInvalid()) type = getType(); + if (type != TYPE_INT) { + genSuffix(type); + return; + } + var num = randLog(MAX_UNARY_EXPRESSIONS_PER_EXPRESSION); + while(num-->0) { + sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]); + } + genSuffix(TYPE_INT); + } + + /** + * Generate + */ + public void genSuffix(Type type) { + if (generateInvalid()) type = getType(); + if (type == TYPE_INT && random.nextBoolean()) { + var t = getType(); + if (generateInvalid()) { + var field = getRandomName(); + genPrimary(t); + sb.append(".").append(field); + return; + } else if (t instanceof TypeStruct s && s.fields.length > 0) { + var field = s.fields[random.nextInt(s.fields.length)]; + if (field.type == TYPE_INT) { + genPrimary(t); + sb.append(".").append(field.name); + return; + } + } + } + genPrimary(type); + } + + /** + * Generate a primary expression. + */ + public void genPrimary(Type type) { + if (generateInvalid()) type = getType(); + var rand = random.nextInt(10); + if (rand == 0 && exprDepth != 0) { + sb.append("("); + exprDepth--; + genExpression(type); + exprDepth++; + sb.append(")"); + } else if (rand < 6) { + genVariable(type); + } else { + genConst(type); + } + } + + /** + * Generate a constant. + */ + public void genConst(Type type) { + if (generateInvalid()) type = getType(); + if (type == TYPE_INT) { + var rand = random.nextInt(10); + switch (rand) { + case 0 -> sb.append("true"); + case 1 -> sb.append("false"); + default -> sb.append(random.nextInt(1<<(rand-2))); + } + } else if (type instanceof TypeNullable n) { + if (random.nextBoolean()) { + sb.append("null"); + } else { + sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name); + } + } else { + sb.append("new ").append(generateInvalid() ? getRandomName() : type.name); + } + } + + /** + * Generate a variable. If there are none generate a number instead. + * If the program is allowed to be invalid a random name can be generated in some cases. + */ + public void genVariable(Type type) { + if (generateInvalid()) { + sb.append(getRandomName()); + return; + } + if (generateInvalid()) type = getType(); + var t = type; + var v = findVisibleVariablesMatching(va->va.type.isa(t)); + if (v == null) genConst(type); + else sb.append(v.name); + } + +} @@ -29,6 +29,7 @@ <module>chapter08</module> <module>chapter09</module> <module>chapter10</module> + <module>chapter11</module> </modules> <properties> |
