Replies: 20 comments 17 replies
-
This week was finally the week I decided to redo everything in rust, so most of my time was spent actually reimplementing all the library features like basic block identification, Call Graphs, CFGs, and Dataflow. I also added some traits for graphviz dot generation which lets me do some very concise and pretty code to generate callgraphs where each node can be anything from a single node, a CFG, or a Dominator Tree (see here). Calculating DominatorsThe dominators were calculated using the given algorithm, but I decided to implement it using a worklist just to avoid rewriting similar code. For this, I set my Everything ElseNext, I needed to calculate the Strict Dominators, Immediate Dominators, and Domination Frontier for each block. I realized that each of these could be calculated solely from the Dominators in relatively non-slow algorithms.
# For a given block x with strict dominators sdom[x]
# These nodes dominate x's strict dominators, and thus cannot be immediate
non_immediate_doms = union(sdom[i] for i in sdom[x])
# There should be only one dominator that satisfies this (assuming sdom is valid)
immediate_dom = (sdom[x] - non_immediate_doms)[0]
# For a given block x that dominates dom_by[x]
# These nodes are the successors of all of x's dominated
successors = union(succs[i] for i in dom_by[x])
# The successors that are not dominated become the frontier
frontier = successors - dom_by[x] Graph generationI used Graphviz to generate dominator trees as well as a a special pass that finds the unique block in a bril program named In the example shown below (a simple nested loop), the dominator tree contains the actual CFG edges dashed, while the actual dominator tree is in black. On the right, the red node is selected, with its strict dominators in dark green, the nodes it dominates in light green, and its dominance frontier in blue.
TestingFor testing I did a very slow brute force method - I used DFS to generate all (non-cyclic) paths from the entry node to a target node SummaryI spent most of my time this week actually restructuring all my old python code and I am now pretty happy with how everything is laid out now! I think the most interesting part of this assignment was definitely calculating all the other dominance relations from the initial dominators, but otherwise the algorithm was fairly straightforward and easy to implement after I decided to reuse my worklist implementation. I would say I deserve a star just because I spent a bunch of time to make the graphs look good and I personally quite like them. |
Beta Was this translation helpful? Give feedback.
-
I implemented all three dominator functions in TypeScript here: https://github.com/mt-xing/cs6120/tree/main/l5 For the most part, I just followed the algorithm given in class to compute dominator nodes. Once I had those, I could compute the tree by sorting all the nodes by number of dominators, least to max, and then just constructing the tree by walking each node down from the root. To get the frontiers, I inverted my dominator mapping to get me the list of what each node dominates (instead of what dominates it), and then just looped through all of those node successors, checking if they're already in the dominated set. Also, up until now, I'd been representing my CFGs as maps from blocks to sets of other blocks that are their successors, but this representation got annoying enough to work with that I bit the bullet and wrote some helper code to convert the map into a proper graph data structure with actual pointers to other blocks, both successors and predecessors. To test my implementation, I hooked into my Bril testing library from L3, with some minor retooling. Instead of running the interpreter and checking for identical output, I could instead manually verify domination relationships by recursively DFS-ing backwards from any given dominated node through its predecessors. If any given backwards path either looped or resulted in the node I expected to find as the dominator, the path was okay, but if any path made it to ENTRY without hitting the expected node, the path was marked as bad. I could then need only assert that all paths were okay and none were bad to guarantee that all the claimed dominators were correct. Using the same method, I also tested that nodes in the dominator tree correctly dominated all their (recursive) children, and that the domination frontier consisted of nodes that were not dominated but who had at least one predecessor who was. With this test harness in hand, I was able to test my implementation against the entirety of the bril benchmarks and bril tests folder to verify correctness. This revealed quite a few bugs, basically all of which stemmed from me going off my sketchy notes from class intead of reading the online notes, as I forgot to initialize the I believe this deserves a Michelin star as the extensive testing I performed against the hundreds of benchmark and test files allows me high confidence that my implementation is fully correct. It's also nice to see the previous engineering investments I made begin to pay off, as I was able to leverage lots of old CFG and testing code to make my life easier and code more correct. |
Beta Was this translation helpful? Give feedback.
-
I implemented the algorithm for computing the dominator sets that we discussed in class. It was pretty straightforward, and I don't think there were any weird corner cases I had to consider. It is nice to be able to use code that I've written previously to make things easier. I'm building up a pretty nice utils crate lol. Once I wrote the code to compute the dominator set for a node, I implemented a function that will prune the set of everything except the nodes that it immediately dominates. This was nice for building the dominator tree. I used some graphviz stuff to output the dominator tree as an actual graph, which is nice for visualizing stuff. Finally, I also wrote some code to compute the dominator frontier. I tested my code for computing the dominator set by brute-forcing it, sorta, and comparing it against that. I used a bfs to accumulate all the non-cyclic paths to a node, took the intersection over all of that, and made sure that all the nodes in that resulting set were in the dominator set my algorithm computed. I tested it on all the core benchmarks. For the immediate dominators and dominator frontier, I inspected the output from a bunch of benchmarks and manually verified it was right. I thought this less-rigorous approach was fine, since they only required small add-ons to the main algorithm for computing the dominator set. |
Beta Was this translation helpful? Give feedback.
-
I implemented the three algorithms mentioned on the lesson page (dominators, dominance tree, dominance frontier). I started with a goal of re-using my worklist algorithm from the previous lesson, but I realized that all of the dataflow algorithms I implemented had been functions of the block contents while the dominator algorithm is a function of the blocks' labels. So it would have required a fair amount of plumbing to make it work. In the end I just implemented the dominator algorithm from scratch. The dominance tree was pretty uneventful (although implementing a version that output the tree in dot once again made debugging much, much easier). The dominance frontier was more interesting because I had an intuition about what the dominance frontier was and that intuition turned out to be completely wrong. For example, loop bodies seem to often have loop headers in their dominance frontier. It made sense when I thought about it, but I had a feeling that loop headers come "before" loop bodies and only things that come "after" you belong in your dominance frontier. Another oddity was how many blocks have no dominance frontier at all. The roots and leaves of dominance trees have no dominance frontier by definition so in small functions it was common for me to find that all or almost all dominance frontiers were empty. So mark that one as another win for "implementation deepening my understanding". For testing, I just enumerated paths (without excessive looping) from entry blocks and made sure every path that reached a given node included all of its dominators. Then I used turnt and hand-verification to round everything out. |
Beta Was this translation helpful? Give feedback.
-
I implemented the three algorithms discussed in the lesson: computing dominators, building the dominator tree (with some primitive visualization), and computing the dominance frontier. I started with the standard iterative approach for computing dominators, which refines sets until they converge. The algorithm itself is simple, but I had to make sure the CFG had a unique entry block. I initially overlooked this, which led to weird cases where dominator sets didn’t make sense. The dominance frontier turned out to be the most interesting part. I had an intuition that was completely wrong—I initially assumed a block’s frontier only contained things that came “after” it, but in practice, loop headers frequently show up in their own dominance frontier. It made sense once I thought about it: if a loop body has a back edge to its header, the header needs to be in the frontier. For testing, I verified the dominator sets using a naive path enumeration algorithm (on the bril benchmarks)—essentially listing all paths from the entry block and making sure every dominator was present on all of them. It’s slow, but it works for small cases and confirmed that the results matched expectations. For dominance frontiers, I manually checked a few tricky cases, mostly focusing on loops and conditionals to make sure the results were reasonable. |
Beta Was this translation helpful? Give feedback.
-
Dominators, Tree, and Frontier (1) uses the algorithm and initialization presented in lecture. (2) uses the program's cfg combined with the map from (1) to find children satisfying the immediately dominates relation. (3) uses the map from (1) and the cfg to find the undominated successors of each basic block. Verification I inlined the correctness checks in the same script as the implementations such that the original bril program was emitted to stdout only if the analyses passed their associated checks. Then I ran all of the core benchmarks through the analyses using brench, and found that all produced valid output. Since my dominance frontier check gave a false negative, I made sure that the SSA conversion algorithm that used the implementation produced valid SSA on the core bril benchmarks. Challenges: |
Beta Was this translation helpful? Give feedback.
-
In collaboration with @bryantpark04 and @dhan0779 Dominators: Getting the dominators for each node was pretty straightforward, and we followed the algorithm discussed in class with a reverse post-order traversal for linear time complexity. We didn’t face any major issues with this step, although we did make small modifications to our previous CFG implementation and utils to make the interfaces cleaner and more usable. Dominance Tree: The dominance tree builds off the dominator mapping. From the inverted mapping, we can identify for each current node which of the nodes it dominates are also dominated by another node it dominates, in which case the current node is not its immediate dominator. The resulting mapping from each node to the nodes it immediately dominates becomes our dominance tree. Dominance Frontier: We determined the dominance frontier for each block using the mapping of nodes to their dominators produced in the first part of this task. We inverted the mapping to get the set of blocks dominated by a given block, and we found the frontier by selecting the set of successors of dominated nodes which were not also strictly dominated by the current block. One small issue we ran into was forgetting the "strictly dominated" aspect of this last step, which resulted in some initial outputs which did not include nodes in their own dominance frontier. Testing: We tested our dominator implementation using a brute-force approach to BFS over all paths from the entry to each block; we expect the intersection of nodes on all the paths to be equal to the set of dominators. Similarly, for each block and each of its dominators, we performed a BFS from the entry to the block and pruned branches as we encountered the dominator. If we ever encountered the block before the branch was pruned (meaning we found a path without the dominator), we would throw an error indicating the dominator set was incorrect. We ran these checks over all the core Bril benchmarks to verify our dominator set computation. We also manually tested our dominance tree and frontier implementations over various benchmarks, particularly those with tricky branching and loops, to check we handled edge cases correctly. Conclusions: Overall, the implementations were straightforward, with only occasional edge cases and sometimes a need to tweak our prior representation of structures and utils. We believe we deserve a Michelin star since we implemented each of the three algorithms and carefully thought about and tested their edge cases. |
Beta Was this translation helpful? Give feedback.
-
DominatorI combined the algo discussed in class with previous generic worklist solver. Dominance TreeDominance tree can built from the dominator set found in previous step. For each cfg node, we sort its dominator set in ascending order based on the number of dominators each dominator has. This forms a path from dominance tree root to the current cfg node. We are able to reconstruct the dom tree by progressively adding new path starting from root to existing tree. Dominance FrontierFor a given cfg node, we just take one step forward from all the nodes in the sub dominance tree rooted at the current node. I initially made a mistake by excluding the current node when computing its dominance frontier. I just realize this after today's SSA lecture. In my previous impl, for those leaf nodes in the dom tree, they won't have any dominance frontier to insert the phi node. This is apparently wrong in a simple diamond-shaped cfg case. Here is a sample graphic output for bitwise-ops benchmark. Dominance tree is drawn alongside original cfg graph. In this particular example, TestingI test my program by brute forcing all of the paths from entry node to any particular node in the original cfg and keep taking intersection of nodes in the path that leads to the destination node. I then check whether the dominator set found with this brute force method is the same as the one found with my worklist algorithm. I tested my program against all general core benchmarks. ConclusionOne of the problem is handling some edge cases in the definition of dominance frontier, for example, dominance can be reflexive and a node's dominance frontier can contain itself. I think I deserve a Michelin star since I completed three tasks, did throughout testing and did some extra visualization stuff. |
Beta Was this translation helpful? Give feedback.
-
Here is the code: link Overall, I did not find this assignment very hard. I've implemented all three parts and checked them on a few benchmarks. I also made sure that there were no infinite loops in my implementation on all the benchmarks from the core set. To find all the dominators, I used the pseudocode from the slides (i.e. not the worklist version). Here are the visualizations for the gcd benchmark: I was a bit sick this week and I did not implement the proper testing. I routinely use copilot when coding. It helps me to write repetitive code. I rarely find "long suggestions" (when copilot writes the whole function) useful; most of the time I remove all such code. I did not use any text prompts for this assignment. |
Beta Was this translation helpful? Give feedback.
-
Dominators: code I implemented the tasks in Haskell. To compute dominators, I used my existing dataflow framework: this made it pretty easy, only like 10 lines. I was actually quite pleased with the extent to which I was able to make the overall implementation fairly concise: with a few minor changes to the rest of the code, it ended up just ~50 lines for the dominators computation and tests. I think this speaks to the utility of the general-purpose dataflow framework, since I was able to reuse it for this task. While implementing, I noted that some things don't really make sense for unreachable basic blocks, so in my main function I removed unreachable basic blocks before doing any other processing on it. I already had a function to do this from previous assignments. For tests, I implemented a very slow operation that checks if block A dominates block B, by simply doing a BFS from the start node, skipping block A, and seeing if block B is reachable. This is true iff A does not dominate B: if A dominates B, then if you remove A, then B should become unreachable. I then tested for all pairs that this agreed with what my dominators function returned, and also agreed with what the tree I computed said. I ran these tests on all bril programs in the core benchmarks directory, and they all passed, so I am pretty confident it works. I think this merits a star. |
Beta Was this translation helpful? Give feedback.
-
Code: https://github.com/ethanuppal/cs6120/tree/main/lesson5/dominators I implemented these algorithms with a Rust CLI/library with
Testing dominatorsI was thinking about this for quite some time. The brute force solution will iterate over all possible paths from the entry to a given node and check that a purported dominator really appears on each path, but (of course) there are an infinite number of such paths. One could try restricting to a certain number of loops around a cycle, but an implementation of DFS, for example, that allows visiting the same vertex again up to a limit has the possibility to get stuck in reducible CFGs, I think. I was initially thinking of something similar to Bellman Ford where I look at paths up to The next idea was to think about fuzzing paths. It is already nontrivial to fuzz simple paths from entry to a given node, and this alone is not sufficient since it only takes one cyclic path avoiding the purported dominator to invalidate the instance. It is probably extremely difficult to have a uniform fuzz over paths that can loop around a natural loop zero or once. I'm really curious on how brute-force testing paths in CFGs is possible, given that even simple paths are annoying to fuzz over and are very unrepresentative. Practical testing for dominatorsI ended up simply using the oracles that Professor Sampson wrote under Professor Sampson's implementation has additional basic blocks that I did not compute (I specifically implemented my CFG builder to produce the minimal number of basic blocks required for lossless conversion). Thus, I wrote a silly This is the relevant CI code: - name: Test dominators
run: |
cd lesson5/dominators
python3 ../../lesson4/dataflow/match_outputs.py \
"python3 ../../bril/examples/dom.py dom | jq 'del(.entry1, .b1, .b2, .b3, .b4, .b5, .b6, .b7, .b8, .b9, .b10) | with_entries(.value |= map(select(. != \"entry1\" and . != \"b1\" and . != \"b2\" and . != \"b3\" and . != \"b4\" and . != \"b5\" and . != \"b6\" and . != \"b7\" and . != \"b8\" and . != \"b9\" and . != \"b10\")))'" \
"cargo run --quiet -- --algo dom | jq" \
../../bril/examples/test/dom/*.bril \
../../bril/benchmarks/**/*.bril
python3 ../../lesson4/dataflow/match_outputs.py \
"python3 ../../bril/examples/dom.py tree | jq 'del(.entry1, .b1, .b2, .b3, .b4, .b5, .b6, .b7, .b8, .b9, .b10) | with_entries(.value |= map(select(. != \"entry1\" and . != \"b1\" and . != \"b2\" and . != \"b3\" and . != \"b4\" and . != \"b5\" and . != \"b6\" and . != \"b7\" and . != \"b8\" and . != \"b9\" and . != \"b10\")))'" \
"cargo run --quiet -- --algo tree | jq" \
../../bril/examples/test/dom/*.bril \
../../bril/benchmarks/**/*.bril
# we omit cases where the Bril program seems to be maybe malformed
python3 ../../lesson4/dataflow/match_outputs.py \
"python3 ../../bril/examples/dom.py front | jq 'del(.entry1, .b1, .b2, .b3, .b4, .b5, .b6, .b7, .b8, .b9, .b10) | with_entries(.value |= map(select(. != \"entry1\" and . != \"b1\" and . != \"b2\" and . != \"b3\" and . != \"b4\" and . != \"b5\" and . != \"b6\" and . != \"b7\" and . != \"b8\" and . != \"b9\" and . != \"b10\")))'" \
"cargo run --quiet -- --algo front | jq" \
../../bril/examples/test/dom/*.bril \
../../bril/benchmarks/**/*.bril \
--exclude core/is-decreasing.bril \
--exclude core/relative-primes.bril \
--exclude mem/two-sum.bril \
--exclude core/recfact.bril \
--exclude float/euler.bril \
--exclude float/mandelbrot.bril You may notice that a small number of files are excluded from the dominance frontier tests. That's because the oracle (I believe incorrectly, but I'm not sure), considers
part of the same basic block, and considers I believe I should get a Michelin star because I implemented and tested the assigned tasks. |
Beta Was this translation helpful? Give feedback.
-
Group (@ngernest, @katherinewu312, @samuelbreckenridge) Code Our code for finding dominators is a straightforward implementation of the pseudocode discussed in class. Coming up with a way to algorithmically confirm the dominators we compute was probably one of the trickier parts of the task as it involved thinking about different (potentially naive/slow) ways to compute dominators. To check the dominators output by our code, we implemented a version of DFS which enumerates all paths (without repeating nodes) between two nodes within a CFG. We then used DFS to enumerate all paths from the entry node to each node For the dominance tree, each node's children are those nodes it immediately dominates. To implement the tree, which we represent as a mapping from nodes to its successors, we first reversed the dominators set obtained from get_dominators() in dominators.py to obtain a post-dominance set for each vertex in the cfg. For each vertex, we then took the intersection of its post-dominance set with its set of successors in the cfg to obtain those nodes that the vertex immediately dominates. This task was pretty straightforward, and we believe our implementation for this is quite concise. We also decided to use graphviz to generate dominator trees to better illustrate the trees; a visualization can be generated if you specify a --draw option when running the program (see below for what our program generates for loopcond.bril and while.bril in examples/test/dom). We tested our implementation using Turnt with those tests in the Bril dominator examples repo, and verified that our code behaves the same. For dominance frontiers, we implemented a few helper functions which indicate for two nodes We believe our work merits a Michelin star due to the extensive testing we did to ensure our implementations worked correctly, e.g. testing our implementations on all the bril programs in the core benchmarks directory. |
Beta Was this translation helpful? Give feedback.
-
Code: blocks.py For this task I extended my basic blocks library to compute dominators too. I compute the dominators following the algorithm shown in class. Some issues came about from using Python sets, just a bunch of mutation things. I also implemented dominance trees. To do this, I had some helper functions check if nodes are strictly domminating or immediately dominating each other, and then to create the tree it tests it for the relevant properties. My implementation might not be the most optimal speed-wise, but I feel fairly confident in its correctness. The overall runtime is O(n^3), which probably could be improved. I also have a function that can compute dominance frontiers. For testing, I run brench on a script that constructs basic blocks for each core benchmark (which would automatically construct dominators). Then I check if they are valid using some BFS and crashing the programs if they are not valid, therefore failing the test. |
Beta Was this translation helpful? Give feedback.
-
Code is here. I found the actual algorithms-- find dominators, create dom tree, and calculate dom frontier-- to be pretty simple in themselves. I did these parts over the weekend and didn't think they were too hard-- however, I did keep mixing up whether "doms" held dominators or postdominators-- I realized at one point my domtree function had accidentally made the postdominator tree instead of the dominator tree. I kept mixing up whether the value of A in doms was the nodes that dominate A, or the nodes that A dominates. Luckily, this error was quickly fixable by just reversing the CFG at the start of my function 😄 . In general, I do think doing these implementations helped me understand the dominance concepts better; I kept having to go back to clarify things to myself that I thought I understood but apparently didn't (for example, for the dominance frontier, I was momentarily if A dominates nothing, but has B as a child, then is B in its dominance frontier, even though there's not any unique node C such that A doms C and C does not dom B? Answer: yes, this is just the case where C=A). I'm starting to wonder if my CFG representation isn't the most optimal. I keep it as a dictionary of {parent : children}, but I also created an 'end' block, which all exit blocks point to. This has been useful in some cases, but is also an annoying thing to keep lugging around from algorithm to algorithm; instead of looping over the keys in the CFG, I have to remember to loop over the keys in the CFG plus 'end' every time. Still, it's useful to only have to deal with one block with no children in the CFG. The part I struggled with the most here was the testing. I have domtree and domfrontier tests that work, but I couldn't quite get my dominator tests to work-- those pesky cycles! I tested my dominator function on benchmarks in the bril benchmarks computer by hand-computing the intended dominators, though, so I have confidence that my dominator function is right even if I couldn't quite get the test to work. The tests for domtree and domfrontier, assuming that dominator calculation works, were pretty trivial to code up, and I ran those on benchmarks in the bril benchmarks folder; these finding no bugs also supports that my dominator calculation is correct. To be honest, I am less confident that I deserve a Michelin star this time, since I couldn't quite get my dominance test to work by the deadline. If I have time after the deadline, I will come back and see if I can fix it to give myself total confidence in my solution. Still, it wasn't for lack of trying; I spent a lot of time on debugging, and have sufficient tests for the other algorithms for this week, so I'm hopeful that I can still earn a Michelin star for the work I did. |
Beta Was this translation helpful? Give feedback.
-
CodeHere is my domination analysis file and my correctness-check script Summary + How it worksReusing my implementation for basic block and CFG construction, I built out a tail-recursive function that accumulates a map called In order to build out the domination tree, I translated this To build the domination frontier, I wrote code where, for every block A and for every block B, if B is not strictly dominated by A and there exists a predecessor of B that is normally-dominated (i.e. reflexively) dominated by A, then add B to A's frontier set. Finally, still happy to say I'm still using only functional code! TestingI wrote a function that enumerates all paths in a CFG, given an entry node (which I ensure exists because I made a dummy entry basic block). I made sure not to visit the same node twice by keeping a running set of Then, I wrote a Python script to call my binary on every file in the Hardest PartI think the hardest part was implementing the enumeration of the paths. I forgot how to implement DFS haha, and also doing it in a functional style took a little thought to get right. StarI think my work deserves a star! |
Beta Was this translation helpful? Give feedback.
-
I implemented a function that computed the dominance relation, iterating over the CFG in reverse post-order. 1 I then implemented a function that inverts the relation and computes post-dominance. From this, I wrote functions that compute the dominance tree and dominance frontier. TestingTesting was by hand and quite limited; I tested on the BBS benchmark that I wrote since I am most familiar with that program. I additionally hand-tested on a few benchmarks from the core benchmark suite. An issue was not many programs were sophisticated enough (or long enough) to have an interesting dominance frontier. More testing on this front would increase my confidence in the correctness of my dominance utilities. Footnotes |
Beta Was this translation helpful? Give feedback.
-
Summary Side note: Using the networkx library's DiGraph object to represent my CFG was one of the best decisions I made (for my sanity). Previously, I was storing the CFG as two separate adjacency lists -- one for predecessors and one for successors. Using the Networkx library made my code much more concise and easier to debug, which allowed me to focus more on testing and ensuring my implementations are efficient! To output the dominance tree, I loop over the CFG and delete any edges A->B where A is not one of the dominators of B. My solution to output the dominance frontier of a given block A is a little bit naive. I first loop through my dominators graph and collect a list of all blocks B where A dominates B. Then, I loop through the dominators graph again and collect all the blocks C where B dominates C but A does not dominate C. This works, but I'm sure there's a more elegant solution out there. Testing |
Beta Was this translation helpful? Give feedback.
-
I implemented the algorithms to find dominators, calculate a dominance tree, and compute the dominance frontier. The hardest part of the task was probably just understanding the terminology. I didn't realize what a dominance tree was until I had already messed up my implementation, but after actually watching the video things went a lot smoother. I tested my implementation by writing a naive implementation for finding dominators that just traverses every path and calculates the intersection of all paths that travel through a node. I manually verified that the naive implementation output correct results, then compared the naive and optimized implementations for the benchmarks to make sure they returned the same results. For the dominance calculation tree and domination frontier, I again compared the code outputs on the core benchmarks to manually calculated results. I think I deserve a Michelin star for all the manual verification ... |
Beta Was this translation helpful? Give feedback.
-
Partner: Noah Schiff (@noschiff) SummaryOur implementation for global analysis was done in TypeScript, building up on the infrastructure which we had set up in the previous two sections. In line with the way the project was prompted, we had two clear workflows, one in first fleshing out the algorithms as they had been described in the unit, and then implementing slower, more naive and obviously accurate algorithms with which we could verify the correctness of our outputs by confirming that our dominator analyses we indeed correct. ImplementationWe followed the implementations described in lecture and in the videos to develop our implementation for dominators, dominator trees and the domination frontier. A rough summary of our approach is as follows: Dominators Here, we used a TypeScript map to map vertices to the nodes which they dominate. Following the algorithm, we then iterated to convergence, repeatedly intersecting the current set with the set of dominated nodes of predecessors. Dominator Trees For the dominance tree, we again relied on the ideas discussed throughout the videos. Using an inverted dominance mapping (by inverting our dominator map), we then used second-level strict dominance rules to compute the initial set, before finally removing any values present in the second-level set to construct the entire dominator tree. Ultimately, this tree had the same type as a CFG. Dominance Frontier We finally again used the video's techniques to implement the dominance frontier, again using the inverted map structure and analyzing successor nodes in the CFG. TestingTesting was split up into three components initially – dominator analysis, dominator tree analysis, and dominator frontier analysis. We later realized that we could optimize this a bit, and merge the first two of these into a test to determine that a) The correct dominators were generated and b) that they are were correctly represented in tree form. Both test components relied on us modifying straightforward and well-known graph/tree algorithms. Dominator/Dominator Tree Analysis We were given the following premise for dominators (let's define To integrate this into our dominator tree analysis, we expressed the following: So to assist with this process, we developed two functions: Dom The GetDominated This second function, Tree Analysis Another worthwhile test here was just to indeed verify that the dominator tree we had constructed was successfully a tree. The previously test sufficiently shows that all relationships it outlines are correct; a simple, algorithmic verification that our ultimate structure (which, in fact, has the same type as our CFG representation) is a tree, simply involves iterating through each edge. This was done easily by performing a variant of Kruskal's Algorithm with Union Find, and breaking if we ever found a cycle. A Note On Types For our CFG, we use the type Domination Frontier Analysis We outlined the algorithm for this approach around this biconditional statement: In plain language, that Forward Pass Backwards Pass Thus, with both directions satisfied, this completes a thorough test that the dominance frontier has been correctly computed. Further Testing TakeawaysThis was definitely an invigorating assignment focusing us to both use skills in implementing the proposed algorithms for dominator analysis, but also relying on our fundamentals working with graphs and trees to develop good tests. Each subsequent assignment has also given us the opportunity to go back and clean up our code, focusing on writing with good types and correct implementations. This unfortunately was a lot later than desired, but spending the time to really get through this implementation has really ensured our full understanding of the material, and we are excited for this next mission! |
Beta Was this translation helpful? Give feedback.
-
I implemented a few functions related to dominators:
For testing I used a couple simple bril scripts to verify that the algorithms were working as expected, and for more through testing on more complex scripts (in the benchmarks dir) I spent most of my time making sure that the outputs from both of the immediately dominates implementation matched. I'm still pretty happy with some of the decisions in the way i've modeled Cfgs, mainly in that graphs use integer ids that can later be mapped to the labels in the source, as that simplified several parts of the algorithm and allowed me to reason and test the implementations on arbitrary graphs. Overall, I found the assigment fun as to implement a few algorithms on graphs. |
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