Replies: 21 comments 2 replies
-
Beta Was this translation helpful? Give feedback.
-
dominators.py and dominators_test.py Summary
Implementation details
CFGs and Dominator Trees for functions in mandelbrot.bril To implement tests for each of our features, we did the following:
How did you test it? Which test inputs did you use? Do you have any quantitative results to report?
What was the hardest part of the task? How did you solve this problem?
Sources: |
Beta Was this translation helpful? Give feedback.
-
Summary
DetailsDominators of a FunctionI first implemented the naive algorithm described in lecture to find the dominators of a basic block in the CFG. After this, I implemented the reverse post-order optimization. I thought about extending my current CFG data class representation to support the storing of dominators of a node, but I ultimately decided to make a general “node” class that would represent a block. This “node” class can then be instantiated as a CFG block or contain more information in the form of a dominator tree. This had the added benefit of making my visualization functions much more reusable which made me quite happy. Dominance TreeLeveraging the dominator searching function, I implemented a function that would construct a dominance tree given a mapping of nodes to their dominators. This was a simple pairwise search. A potential extension could do some compression on the pairwise searches for better runtime. With the generalized representation of a “node”, I was able to reuse a lot of the CFG visualization code that I wrote for the last task to visualize the dominance tree. Dominance FrontierAgain leveraging the dominator searching function, I implemented a function that would find the dominance frontier of a given node and entry node. The implementation was a couple of lines of set operations that were modeled on the definition of a frontier. Dominators and Dominance frontier Visualization as GIFAs a means to inspect and test my dominator searching and dominance frontier functions, I decided not only to create a graphviz representation of the dominators and dominance frontier, but I integrated the visualization as a GIF across all the nodes in the CFG. I leveraged the library that I implemented from the previous lesson task. In the visualization, the labeling is the following
Quality of LifeWhile refactoring my node class, I decided to also update some of my util functions to be more reusable and have a more general CLI interface. I extended my load function to also do CLI flag validation. Testing/EvaluationI wrote a validator function that takes in two nodes (A and B) and the CFG nodes, and returns if A dominates B. I also implemented another validator function that checks if A strictly dominates B. The function can be easily inverted to check post-dominator relations too. This was a simple brute force check—doing BFS to list all paths from the entry node to B and then checking to see if A is in all the paths. A small optimization can be done for early terminating with cached paths. I also visually inspected the dot graph outputs for the dominator tree and the dominance frontier on some small handwritten examples (small.bril and loopcond.bril) and then extended them to larger test cases in benchmark/core. I also inspected the GIF output. DifficultiesTesting the dominance frontier was a little confusing at first. With the many bril programs that I tested on (including ones from the benchmark/core as well as handwritten ones) a lot of the nodes had empty dominance frontiers. This led me to debug and iterate multiple times on my implementation only to eventually realize that my initial implementation was actually correct. In retrospect, I should’ve tested on an example where I knew what the dominance frontier was from the start (in a true test-driven development style). |
Beta Was this translation helpful? Give feedback.
-
I have implemented dominators, the dominance tree, and the dominance frontier. The highlight of the test was assessing the correctness of the dominators:
The biggest challenge that I had was the reflexivity of the dominance relation, as it created several edge cases.
|
Beta Was this translation helpful? Give feedback.
-
Link to code The pseudo-code from class was straightforward to implement for dominators. To check that all the dominators I calculated for a given node truly dominated that node I used a DFS to enumerate all paths between the entry and that node. Then I made sure each dominator was in every path. I ran my scripts on test programs from the benchmarks directory. Similar to another comment, I had to add explicit edge cases because the dominator relation is reflexive - this gave me trouble for a bit of time. I also was a bit confused about the successors of any node being in its dominators frontier since every node dominates itself, but resorting to the definitions I decided this was correct. |
Beta Was this translation helpful? Give feedback.
-
SummaryI have implemented the algorithm to
To prove the accuracy of the algorithm, I did
TestingBy definition, A dominates B iff all paths from the entry to B include A. Hence, for each of the block b in the cfg, I would iterate through its dominators, and for each dominator A, I would do a dfs from the entry to b to check if they all include A. ChallengesOne of the first challenge was to determine the entry block of the cfg. At first I thought of finding the block that has no predecessors. However, I realized for some of the test programs it is possible that all the blocks have predecessors. Then i realize that the entry block would just simply be the first block appear in the bril file. Hence, I would need to create a dummy block in the beginning and set its successor to be the the current entry block and predecessor to be empty set. This requires me to change the blockname-to-block map I created and the predecessor & successor list. Another challenge is to translate the definition of dominance frontier and dominance tree to actual code. I need to consider whether to think traversing down the tree or traversing from bottom up (e.g. should we use predecessors or successors in constructing the dominance frontier?) |
Beta Was this translation helpful? Give feedback.
-
Summarize what you did.
Explain how you know your implementation works—how did you test it? Which test inputs did you use? Do you have any quantitative results to report?
What was the hardest part of the task? How did you solve this problem?
idom[i] = strict_dominators[i].copy()
for node in strict_dominators[i]:
idom[i] -= strict_dominators[node]
|
Beta Was this translation helpful? Give feedback.
-
SummaryDetails/TestingI computed the To test, I wrote functions to verify all the above relations using naive algorithms. For example, I verified that DifficultiesI was unsure of how to deal with CFGs containing unreachable blocks, since some of the definitions are well-formed in the presence of unreachable blocks and some (namely, the dominator tree) aren't. My original algorithm for constructing the dominator tree would omit any unreachable blocks, but this didn't agree with my verifiers which applied the definitions more directly. I ended up modifying the dominator tree algorithm, which now produces a graph if there are unreachable blocks. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
I worked with @SanjitBasker on this assignment. SummaryImplementation detailsIn this assignment, we implemented some utility functions for global analysis computations using C++. These included dominators for every block in a CFG, the dominator tree constructed from the dominance relationship, and the dominance frontier for every node in the CFG. Just for fun, we also decided to implement an algorithm for finding all natural loops from the dominance information as well. To compute block-level dominators, we modified our dataflow analysis framework slightly from the previous assignment to operate on blocks instead of on instructions. Since we didn't want to have to iterate through the CFG to get all variable names for the top element, we chose to wrap each set of dominators in an optional, with the optional element not existing representing the top element. We also made sure to iterate on the blocks in reverse postorder. Once we got the dataflow analysis framework for the dominators working, computing the other functions was straightforward. To compute the dominator tree, we simply iterated through each block's dominator set and checked which blocks immediately dominated the other, and added those edges to the tree. To compute the dominance frontier, we used a DFS-based algorithm that takes advantage of the fact that, when considering the dominator tree, the dominance frontier of a parent is the same as that of its children, with the parent's immediate undominated successors added. Lastly, to compute natural loops, we first checked for back-edges by verifying that for an edge A -> B, B dominates A. Then, once we found such back-edge, we did a BFS on A's ancestors until we got to B. We then output the set of A, its ancestors we found, and B as the natural loop. TestingFor testing, we implemented a naive algorithm for computing dominators that, for every node, tries all possible paths from the entry node to that node and computes the intersection of all the nodes seen on that path. This is correct because even if there are loops and there exists a longer path to the node, that path must necessarily also go through that node and thus the new nodes will never be dominators. We verified that this naive algorithm and our dataflow analysis output the same result. Just for fun, we also computed the number of iterations it took for each algorithm to compute the dominators. Omitting functions with only one basic block (as those are trivial to compute), the results for each function in each test case of the benchmark suite are shown below. DifficultiesIt was also a little tricky to deal with the special case of the entry node, so we ended up hardcoding that in to our worklist algorithm. We also dealt with some bugs with regards to differentiating the nullopt and empty set values in the optional we used for computing dominators. |
Beta Was this translation helpful? Give feedback.
-
SummaryFor this assignment, I implemented some dominator utilities such as constructing a dom tree, finding the dominance frontier, testing the correctness of the dom tree, and displaying the dominator tree. To test whether the dom tree was correct, I used a naive algorithm that, for every pair of nodes, Once the dominators were correct, I decided to construct an analysis that would identify natural loops. I added a way to visualize natural loops to my Feeling ambitious, I decided to use this to implement LICM, which was tested by once again running the optimization as another turnt environment. ImplementationFor the dominators, I used the algorithm presented in class to compute a mapping from each node to its set of dominators. Partly for visualization purposes, and partly because I find it easier to think in terms of Here is a simple example of output dom tree: And here is the original CFG: Once dominators were implemented, I decided to make an analysis that finds natural loops. To do this, I identified all backedges in the graph, then performed a DFS in the transposed CFG starting from the head of the backedge until the tail (loop header) was reached. Natural loops identified with the same header are merged, and I actually construct a tree of natural loops to handle nested loops. Here is an example of displaying the CFG with loops identified: With natural loops implemented, I also implemented reaching definitions and LICM. In hindsight, doing this without SSA was a questionable choice, however, it was interesting as I sort of realized that SSA kind of falls out naturally of loop optimizations. For example, to maintain behavior, I needed to rename the defined variables of any hoisted instructions to ensure they only had one definition. Furthermore, phi nodes would allow further optimization of cases where multiple loop invariant computations merge into one use. For this implementation, I just handled that case conservatively. Finally, correctly determining when instructions could be safely moved was also pretty tricky which would obviously be far simpler when the CFG is in SSA form. To determine the instructions that could be moved, I framed the algorithm given in the lecture notes as a data flow analysis pass. After identifying which instructions could be moved, the definitions of those instructions were given unique names, and all uses of those definitions, as determined by reaching definitions, were renamed. This allows for cases where a loop invariant variable is used by a non-loop invariant instruction before the loop invariant var is replaced with a new loop invariant definition. TestingAs mentioned earlier, the dominators were checked algorithmically. LICM was tested by running it through all benchmarks. All stages were checked manually by reviewing some of the resulting graphs displaying the dom tree, CFG with loops, and CFG after LICM and making the results golds with turnt. The result of running all benchmarks is as follows:
Here's a small example before LICM: And after: ChallengesThe tricky part of the dominators was forming the dominator tree. Finding natural loops using this information was relatively straightforward. Doing LICM was definitely the trickiest part. There were a lot of corner cases to consider and things to check. Another tricky thing was handling the function arguments for LICM, which I did by adding a ghost instruction in Reaching Defs that defines all the function args in the ghost start block. |
Beta Was this translation helpful? Give feedback.
-
SummaryImplementationSince my CFG implementation used the Constructing a dominator tree was also quite simple after computing the set of nodes idom'd by a given node. I used I also added some quality of life updates to my CFG implementation by making it easier to relabel basic blocks and automatically labelling unlabeled blocks to make it easier to debug. Here's an example output on the
ChallengesSince the implementation of dominator analysis came for free with my graph library, most of the challenges I faced with this project involved understanding how to actually manipulate the data structures returned by the algorithm. There was very sparse documentation provided by TestingI thought it was fair to assume the correctness of the provided algorithm, but I did some simple testing by doing some path analysis using DFS. I also visually inspected the outputs of the dominance frontier and dominance tree as a sanity check. |
Beta Was this translation helpful? Give feedback.
-
SummaryFor this task, @emwangs and I implemented dominator analysis. You can find the repository here. Implementation Details
Challenges
Testing/Evaluation
To verify our dominance frontier, we also made a new Bril program that has a more interesting control flow. This was the resulting dominator tree: And this was the output from our program:
And for reference here is the program:
|
Beta Was this translation helpful? Give feedback.
-
@xalbt and I worked together on lesson 5. Summaryrepo Implementation
Difficulties
Testing
Example graphviz for quadratic.bril
|
Beta Was this translation helpful? Give feedback.
-
SummaryI started with the algorithm shown in class to find all dominators for a given node. ImplementationI used the run-until-no-changes algorithm from class to find the dominators sets for each node. I then inverted the dominators sets to get a "dominated_by" set, which given a node Although the dominated_by set wasn't explicitly part of the task, it made sense to implement because it made finding the dominance frontier very easy. My strategy for the dominance frontier was to do a depth-1 BFS from all the nodes dominated by a given node, collect it all into a set, and then subtracting the original set of nodes that were dominated by the given node. One nuance I found through testing, however, was that for the set subtraction part I needed to make sure it did not subtract the given node itself, as the dominance frontier definition only states to remove nodes with the "strictly dominates" relation. Then came another calculation that was not part of the task, which was finding the immediate dominator for a node. Finally I made the dominator tree by linking each node with its immediate dominator. TestingAs initial validation for the correctness of my dominators algorithm implementation, I checked the algorithm's output for crafted bril programs against my manual construction of the dominators sets for each node. I made sure to test edge cases in the graph such as having incoming edges into the entry block and having unreachable blocks (from entry block) within the CFG. Next, I computationally tested my implementation by writing a simple naive algorithm that also computes the dominators set, and verifying that the results from both the naive algorithm and the "efficient" algorithm matched. I didn't get to implement this, but the graph library I've been using ( ChallengesSome specifics of the dominance relations definitions were kind of tricky. For example, the dominance frontier definition uses strict dominance instead of "regular" dominance. The main implication this had when testing my implementation was that a node can be on its own dominance frontier, for example if it dominates an immediate predecessor. There were also annoying edge cases I discovered relating to the entry block, which I simply had to make special cases for. For example, when writing my naive algorithm for testing, I couldn't apply the definition of dominators using paths from entry to node when the node was the entry block. This example is kind of a technicality on whether not traversing any edges counts as a path from A to A but there were several places where I did something special for the entry block. I had the idea of adapting the algorithm shown in class to calculate the immediate dominator as opposed to the entire dominators set for each node. Having the immediate dominator makes constructing the dominator tree trivial (as mentioned above) and it also allows for deriving the entire dominators set in an easy way, by traversing up the tree (or following the immediate domination relation backwards). However, I got sick and wasn't able to get it working, so I ended up using the algorithm from class and deriving the immediate dominators in a later step. |
Beta Was this translation helpful? Give feedback.
-
Implementation
TestingFor each of the utilities, I wrote a slow but obviously correct algorithm to test my implementation. I then ran the verifier on all of Bril's benchmarks using Turnt (toml file).
Difficulties
Here is an example taken from the Dominance Frontier: |
Beta Was this translation helpful? Give feedback.
-
SummaryImplementation
Testing
Difficulties
|
Beta Was this translation helpful? Give feedback.
-
SummaryIn this task, I implemented three programs that:
Implementation
Testing
ChallengesIt took me most of the time designing the path finding algorithm to check the dominance. I need to make sure I checked every possibility, but only go around each loop exactly once. An overly loose break condition is easy to check as the algorithm will be trapped in a loop. However, it took much effort to check if there is a missing path. To tackle this challenge, I made many artificial control flow graphs that have no actual instructions but just the graph structure. These ad-hoc graphs include multi-branching, nested loops, twisted loops (like "8"), etc. |
Beta Was this translation helpful? Give feedback.
-
SummaryIn this task, I
ImplementationTo build a dominance map from a given CFG, I just followed the algorithm provided in the lesson notes, and it was not hard to implement (though I started with implementing "backward", i.e., post-dominance, so I needed to switch everything back to regular dominance implementation). To build the dominance tree, I first computed immediate dominance map and use that map to find the children level by level, starting from the entry node. (It's worth mentioning that C++'s
TestingTo test the dominance relation, I verify if |
Beta Was this translation helpful? Give feedback.
-
SummaryIn this task, I
ImplementationTo implement TestTo implement the DifficultyIn such a function the
|
Beta Was this translation helpful? Give feedback.
-
Source
Implementation and issuesFor finding dominators, I took the intersection of each predecessors' dominator and reflexive adding. For the dominator frontier, I checked that the successors of each vertex are dominated by the current vertex. Reflexive relations within dominance was unexpected and I did not catch this behavior until I read other's implementations. TestingI didn't have enough time to make an automated tool for verification, but with the set of examples from the |
Beta Was this translation helpful? Give feedback.
-
In this task, you have implemented some fundamental operators on CFGs involving dominators!
Beta Was this translation helpful? Give feedback.
All reactions