Replies: 19 comments 37 replies
-
SSA ConversionHere is my code for converting Bril functions into and out of SSA. It contains two functions:
The implementation mostly follows the class pseudocode. First step is recovering a mapping between blocks, and the set of variables for which TestingI did the most basic type of testing. I wrote a These two combines confirmed that the SSA conversion is correct. The average overhead for these small programs was 34% i.e the roundtrip programs executed 34% more dynamic instructions on average. ExperienceThis was indeed the hardest assignment yet with so many corner cases that need to be considered, especially in Haskell (why do I hate myself?) The two big ones are variables undeclared in a branch and how to incorporate function arguments. I'll briefly talk about how I handled these two: Undefined VariablesThere are two types of undefined variables that I encountered: variables that are explicitly undefined in another branch (eg. for already SSA programs) or variables in loops. For instance consider this already SSA program:
The variable
In this case My solution handles this in two ways: detect if the variable has been defined along a certain branch and only add to the successor's
The definitions
are merged into the
So that the definitions
This way the definition of Function ArgumentsConsider this program
There is a very subtle problem here. To convert this, we need to add a
The the translation can proceed as follows:
|
Beta Was this translation helpful? Give feedback.
-
SSA and fun stuff!!here is my implementation Available options:-h, --help print this help text ExperienceI implemented the to ssa phase to test the last assignment, so that took a little weight off. The most annoying part of the actual implementation was probably dealing with values not being defined on every path, so i used a special value: How to design a CFG?????alas we return to my lack of skills with choosing data structures lol. I have moved to an object based cfg, where there are basic block objects that keep track of their instructions and label separately. There is also a field for the "outgoing edge(s)" which is either ret, br, or jmp, but the br is redundant since that instruction needs to be in the instruction list. The jmp does not, as it can be completely determined by its label, and we can reconstruct the code by tracing through one lucky path that will be just fall through. Now, where to φ functions fit into this? Initially, i added a separate slot in my basic block class for them, but this, it turns out, is not the move. It is much more convenient to just push them onto the list of instructions, so I did that. Beyond that for implementing SSA, I didn't use a hashmap or the stack structure described in class. I will mention this again when i talk about GVN, but it seemed much more intuitive to me to use a persistent map (probably some kind of balanced binary tree). While the runtime isn't as nice as for other "better" environment data structures, it should be fine here, and it wouldn't be so hard to swap out for something with better Ο. The really convenient part of this structure is someone else implemented it so I don't have to. SSA -> not so static anymorethe "easy part" (apparently). This was a little more subtle than I expected it to be, and I was barking up the wrong (maybe) tree for a while with undefined values. I ended up initialising anything that was EVER undefined to 0 in the start block, which is something of a penalty for runtime, but given that there is no way to jump into the start block, it's constant. I feel like there really should be a better way of doing this, but maybe this is a byproduct of the lack of undefined behavior in BRIL (not that this is a bad thing!!). GVNI shamelessly stole the implementation from this paper, since I was having some trouble getting it to work on my own. Hopefully this isn't too egregious, as it is similar to the level of pseudocode we see in lecture, though I did give it an honest effort without this. The main thing I was missing was the order of the traversal and pushing changes to the φ functions in the succ blocks. Unfortunate. After copying this algorithm, some programs worked right away, which is always quite the joy, but certainly not all of them. I had to make a couple adjustments: Some things didn't have canonical homes when I looked them up, so I changed the lookup behaviour to return the key back to you instead of NIL when the lookup failed. This is to be expected, as it happens when there are arguments to the function. There is probably a more "elegant" solution to this, but this was what I could think of that was easy to implement, and easy to convince myself it was correct. The next issue I ran into was with effectful operations: if you malloc two arrays of size n, GVN should NOT alias them, same for calling the same function a few times. I fixed this by changing the representation in my value table, and uniquifying the op when it was effectful. This algorithm also called for a "context" data structure, which the paper described as a hashtable that could snapshot and rollback to those snapshots, so i whipped out the persistent map again, which worked well. TestingFor the life of me, I could not get brench to work, and i'm really not sure why. Luckily, I was able to reimplement most of the functionality in bash, since python is still hard for me to understand. I run all my different options, put the dynamic instructions into a CSV, and append stdout to a file for each flag. After each run, I print the md5sum of each of these files, and can visually inspect if they are the same. This could be automated pretty easily, but it wasn't necessary, and I was more interested in the CSV. We want to see how these things changed the performance, so I ran through the output.csv with a small awk script to collect averages and print them nicely. I normalized everything and show average percent speedup:
We can see that SSA results in ~34% slowdown over the initial code, with the round trip back to non-ssa adding another ~1.6%. The global value numbering recovers about 5% of this performance, but not nearly enough to offset the SSA. This could probably be improved with a more impressive GVN implementation; i pretty much only did syntactic equivalence and commutativity. Copy + constant propagation would likely help some more, and if i get it implemented before midnight i'll update here. UPDATEI'm going to leave the above part as is, as a way of documenting progress. I have since updated the global value numbering to do constant propagation as well as be aware of the
This is really cool if you look at the third row, since there was actually a 7% speedup using global value numbering over the initial code. The reason that gvn is faster than gvn-ssa is that I ran trivial dead code elimination on everything when it was done, and this gave more opportunities to get rid of code. I find the fact that there is a 70% speedup over the SSA code pretty impressive. The typescript compiled benchmarks are surely at least partially responsible for this, as they do a LOT of copying, so adding the Challengesso, when we eliminate redundancy this aggressively, it is possible to arrive at
which is a problem, since I was working with the assumption that the phi functions are all calculated in parallel (which isn't true, but I didn't think of them as affecting each other even when they could) Initially, I thought i could try to reorder them to avoid this, but it is possible to have cycles. I ended up introducing more temp variables, so the above code would turn into
This exposes an interesting property of SSA that I hadn't really thought about. In the φ functions, there are potentially two "versions" of a variable: the one that comes in, and the one that has been defined by the φ function, so this took some extra care, but even with these extra moves, there is a speedup! I think it should be possible to look for cycles, do some kind of DFS through the dependency graph of φ functions, and then use fewer moves to fix things when a cycle appears, but I am wayyyy too tired to implement that right now, so maybe another day :) |
Beta Was this translation helpful? Give feedback.
-
My code is here. TestingI tested in two ways. The first was to run The second way was to run all of the test programs in to_ssa (with made up arguments if needed), and save those outputs using ImplementationAt first, I noticed that figuring out all of the ways that a variable could get named is really similar to reachability analysis. So, possibly unreasonably, I tried to do my After that adventure, I stuck closer to the class pseudocode. "Add a phi node if we haven't" turned out to hide a ton of complexity around when exactly a phi node should get added. I still wound up using my reachability analysis to figure out what should get added, but there were a few snags around how to handle definitions which are coming from the block that we're currently considering adding to. In retrospect, it makes sense that SSA is tricky in the case where a block both defines and uses a variable. In the end, I wound up adding a block's successors instead of the block if I wanted a phi statement that pointed to the block that was currently running, and creating a new entry block if I need to use a phi block to refer to the argument if the code starts running. I also thought that a phi block would always need to return defined variables, but looking at some of the output for the test code, I realized that it was okay to return __undefined if we wouldn't actually use that variable on that code path. I didn't actually do from_ssa yet, though I'll update this comment in a clearly-after-midnight section if I do. Submitted after deadline:After the deadline, I finished TestingI tested this by just doing an ssa roundtrip ( ImplementationThis wasn't nearly as bad as to_ssa, though there was an issue where the id instructions being generated by getting rid of phi instructions could build up and refer to each other, and so I had to jankily put them into an order that works (implemented here). |
Beta Was this translation helpful? Give feedback.
-
SSAMy code is here into SSA transformation
It can be tested as follows, , which is actually running:
One thing worth mentioned is dealing with variables that are undefined along some paths. I need to look at the stack and see if the argument variable in phi node is actually defined or not. Set it as I also spent a lot of time in the beginning thinking of how to determine the number of out of SSA transformation
It can be tested as follows, which is actually running:
SSA Roundtrip TestWe can also stitch the Here It can be tested as follows, which is actually running:
Limitations
|
Beta Was this translation helpful? Give feedback.
-
SummaryMy code is located at: ssa, dce, gvn. Tests are at to-ssa, from-ssa and gvn. I implemented to-SSA and from-SSA, and then tested the transformations to make sure they worked correctly. This included using them in a "round-trip" fashion. To check correctness of SSA form, I use the is_ssa function every time a transformation is done to make sure the program is a well-formed SSA program. To try to remove more phi nodes, I tried global value numbering using the dominator tree approach as documented in Value Numbering by Briggs, Cooper and Simpson. I then added on aggressive dead code elimination from the powerpoint slides: http://www.cs.cmu.edu/afs/cs/academic/class/15745-s12/public/lectures/L14-SSA-Optimizations-1up.pdf TestingI created tests as before for both the Bril to SSA pass and SSA back to normall brill pass. For the SSA back to normal Bril tests, I used tests from the original to-SSA output. I compared outputs without to-SSA, at the midpoint after to-SSA was run, and then after from-SSA was run, making sure they were identical. For GVN testing, I wrote GVN tests and ran the GVN pass making sure the resutls were the same before and after. I also tried some larger programs, using the Bril benchmarks that use integer instructions, but not floats nor pointers. For these, I checked GVN on the program, making sure the result was the same as not running the pass. OptimizationFor most of the Bril benchmarks, GVN was not able to identify much optimization potential. The trivial DCE also has some issues to identify dead instructions in basic blocks, if the instruction was used in the defined and used, but not used after some basic block in the control flow graph. I instead wrote a DCE pass on top of the SSA to identify instructions that were dead. Initially, I wanted to use a worklist style algorithm to remove more instructions, but could not figure out the relationship between different basic blocks and which instructions could be killed. I eventually used the algorithm documented in: http://www.cs.cmu.edu/afs/cs/academic/class/15745-s12/public/lectures/L14-SSA-Optimizations-1up.pdf Interestingly, the DCE algorithm finds many dead instructions, most of which are Phi nodes that the GVN algorithm fails to eliminate. For example, running the sum-divisors.bril benchmark with SSA using the command Perhaps it is not surprising to see that DCE cleans up so many instructions, because when using bril2txt to examine the optimized program, many of the original phi nodes created by the to-SSA pass are eliminated. Meanwhile, these phi nodes are no redudant, so they are not eliminated by GVN. However, all of these optimizations are actually end up adding instructions, as running ChallengesOne challenge was that I could not get brench to work. I wanted to compare the outputs,rather than the number of dynamic bril instructions executed, but I could not get this work. Other challenges relate to SSA, where there were many edge cases, and I had to debug many different tests to get the implementation to work. Edge cases also caused issues in global value numbering, for example, with arguments, that did not have a number. |
Beta Was this translation helpful? Give feedback.
-
My code is here. I think my code for I tested my code with turnt throughout. I made three directories, one each for to_ssa, from_ssa, and the full round trip. I copied over the examples from the reference repository, which has handy "expected programs" in SSA form. As suggested once before, I used list sorting to de-noise my turnt outputs a little. I found two(!) bugs in my domination-related work from last week, one of which was exactly in the line of python poetry I was so proud of. No rest for the wicked. I'm not sure if this is what was expected, but there is some cleverness in the way I deal with phi nodes. I only add new phi nodes for new variables; for old variables I extend the args and labels arrays of existing phi nodes. This means that, at the end of the phi-node-adding pass, phi nodes that still just have singleton args are redundant and can be cleaned up. I do this in a quick additional pass before renaming. It is renaming that gave me the most trouble. |
Beta Was this translation helpful? Give feedback.
-
My code is here: to_ssa. So far, I only implemented Original:
SSA + DCE
Some results when running for correctness:
Limitations:
Fun debugging fact: I did not know Python has the 'shallow copy' and 'deep copy' modes for dict(). Spent some time debugging my recursion. |
Beta Was this translation helpful? Give feedback.
-
ImplementationMy implementation can be found here: https://github.com/gsvic/CornellCS6120/blob/l6-ssa/L6/ssa.py. Some details can be found below. DetailsThe main logic is implemented inside Helper ClassesI am using two helper classes for better readability.
TestingFor the testing, I exported the |
Beta Was this translation helpful? Give feedback.
-
In this assignment, I implemented the "into SSA" and "out of SSA" transformations, and tested them using Converting to SSAThere are two steps in the SSA algorithm, the first one is phi-node insertion, and the second one is variable renaming. The pseudocode is very concise and misses lots of details. I was also confused about the phi-node part of the insertion algorithm in the first place and was misled that we insert an argument to the phi-node when we traverse the dominance frontier of
Later then I found that was incorrect. "Add a phi-node to block" means directly adding Also, "add a phi-node to block unless we have done so already" implies the phi-node is of variable The renaming part is also tricky with several details that need to be paid attention to. For replacing the argument names, I added a constraint of not modifying the phi-node arguments. Since some of the phi-node arguments have already been written by the predecessor blocks, it will be overwritten if we do not distinguish the phi-node from other operations. The phi-node renaming is probably the most confusing part -- "assuming p is for a variable v, make it read from stack[v]" -- the pseudocode does not tell us which phi-node argument we should modify and how to do that. I finally found some clues from this lecture note -- "replace the Lastly, I added some enhancements to my SSA algorithm in order to pass all the test cases:
Converting from SSAThis is much easier than converting a program from non-SSA form to SSA form. As the algorithm describes, we only need to add Global Value NumberingI also followed the Value Numbering paper written by KEITH D. COOPER and L. TAYLOR SIMPSON in 1995 to implement global value numbering, which is the dominator-based value numbering technique (DVNT) in Figure 4. The pseudocode is very clear. Since we have implemented LVN in Lesson3, the basic data structure of GVN is quite similar to that one. I used For the part of "adjust the phi-function inputs in s", it is similar to what I did in SSA conversion. We need to first find out the corresponding argument index that refers to the current block, and change that argument to the canonical name. I took the example in the paper (Fig. 5) to test the correctness of my implementation.
My program outputs the following results, which match the expected output in the paper. We can see those operations that calculate the same value are eliminated in
|
Beta Was this translation helpful? Give feedback.
-
Converting into SSAI implemented conversion into SSA using the algorithm discussed in class. It consisted of figuring out where phi nodes were needed, renaming variables, and actually performing the inserting of phi nodes into blocks. I was able to simplify the algorithm for determining where phi nodes were needed by getting rid of the "if we have already added" checks by using a set, which doesn't really make it any more efficient since the check is just occurring in the set abstraction instead -- but it made this code cleaner. With regard to renaming, this is where I spent most of my time. It took me a bit to wrap my head around how the stack was being used to make it all work. This became more clear after reading the Cytron paper. I also encountered a situation where a value comes from a branch that has no definitions of that variable, so that operand should be set as __undefined. I identified this during testing. Lastly, I inserted phi nodes to the start of blocks. It was a little messy passing around Testing conversion into SSAI tested converting into ssa by running all programs in the benchmarks directory against my conversion to ssa script and piped into the Converting out of SSAI implemented the naive conversion out of SSA. It was pretty close to identical to the pseudo code in class. The one difference is that the id instruction must go before jumps or branches to make sure that it gets hit since not all blocks fall through to their successor. Testing conversion out of SSAI used a similar approach as described above. First, I converted all programs into SSA and then back out of SSA, checked against is_ssa script to be the same as it was before running conversion into ssa, since a program could be in SSA initially if variables were not reused, then ran through interpreter to make sure functionality was not changed. Final thoughtsI enjoyed reading through a bunch of SSA related research papers during this assignment. In particular I read through Cytron and Bossinot and looked at parts of Briggs paper. They were quite interesting. I had quite a busy week and didn't have the available time to try and implement some of the fancier things, although I would have liked to. Perhaps I'll take a stab at it in the future if I have some free time |
Beta Was this translation helpful? Give feedback.
-
My implementation is here Usage
to_ssaThis was by far the most challenging aspect of this task. Some initial hurdles I ran into was my phi nodes not being placed in the right blocks. For some reason they would consistently be placed in the block before they should be placed. This led to me figuring out there was a bug in my dominance frontier generation. Then through testing I encountered the previously warned about "tricky part of the translation". I realized prior to this, to maintain well-formedness, I needed to track the types of the variables. This was a straight forward fix where I tracked the type on the first definition of my variable when creating my from_ssaI implemented the naive conversion of from_ssa. This was fairly straight forward. I wish I started with this, as I had some initial misunderstandings on SSA. TestingI used the |
Beta Was this translation helpful? Give feedback.
-
My implementation is here ImplementationI implemented the passes to convert a program to SSA form and then converting back. To SSAApart from following the pseudo code, I think it's important to think clearly about how to decide where the arguments of the phi node come from. The values that are undefined in the immediate predecessors cannot be simply marked as "undefined". For example, the self-loop test case. The From SSAConverting from SSA is more straightforward, adding copy instruction at the end of predecessors does the job. UsageI have a
TestsThere are two set of test cases:
|
Beta Was this translation helpful? Give feedback.
-
My implementation code is available here. To SSAThere were definitely some complexities when trying to implement the pseudo code to cover all edge cases. As many other people have mentioned here, these issues mainly arise from variables being undefined along some paths and function arguments. For a while I was confusing myself over why a bunch of phi nodes were being generated for variables that were only ever assigned once, but this turns out to just be a side effect of the algorithm and these phi nodes can be automatically optimized away with tdce. When varibles are undefined along some path, I just add an From SSAIn comparison to converting to ssa, converting out of ssa is relatively simple, at least in the naive case. I followed the naive algorithm of adding copy instructions and deleting phi nodes. TestingI tested using the to_ssa and ssa_roundtrip test cases. I ensured the tests worked with a mix of spot checking and roundtrip testing. I ran out of time to do a more detailed analysis and brench, but my code seems to work for all the cases I have spot checked. Usage
|
Beta Was this translation helpful? Give feedback.
-
My implementation is here to_ssaI finished the phi-node insertion part of the algorithm, but didn't fully finish the renaming part, which is a bit more challenging than I expected. I will post updates here once that is done. Initially I computed To solve the above problems I implemented a function
The implementation of the above function is interesting. I basically keep one hashmap Then I defined a helper function This allows my to view variable names as array indices. So Similarly, the stack used in the renaming algorithm is now just an array of stacks, each for one variable. Reading from the top is just making a new string of_ssaTODO to be implemented. I will update here once this is done. TestingBefore starting this ssa project it took me a while to debug the previous dominators implementation. I also assumed that this assignment was due Thursday, which is incorrect... Anyways testing will be largely conducted with brench during half-trip as well as round-trips, against the tests in the Updatei finished the to_ssa function using the algorithms from Cytron et al., but it seems to be erroring because of bugs in the dominance tree function... Trying to fix it now. |
Beta Was this translation helpful? Give feedback.
-
My implementation for SSA is here. There were many different edge cases to work through, and I spend most of my time debugging the function to rename variables. I discovered two bugs in my function to calculate a function's control flow graph and realized that my understanding of the dominance tree wasn't quite right, so I reimplemented that function. ImplementationI assume that a function does not contain phi nodes before the SSA pass. To handle cases where a function's arguments are redefined within a loop, I added a preheader block to the function, so
becomes
before converting to SSA form. To handle cases where a variable is only defined along some paths, such as in the examples that Shubham gave, I add arguments to the phi nodes of a block's successors as I go and ignore phi instructions when I am renaming an operation's arguments. Additionally, I only add arguments to phi nodes for those paths along which the target variable is defined, which generally avoids the issues of working with undefined variables. However, I don't know how to deal with cases when a variable is only sometimes defined along a path. If we have an if branch within a loop, where a variable TestingTo test this code, I (1) used the is_ssa.py script to verify that the |
Beta Was this translation helpful? Give feedback.
-
My implementation is here Usage
ImplementationTo SSAConverting to ssa was the most tricky part of this assignment since there were a lot of edge cases that I had to deal with. In particular, undefined values gave me a lot of trouble. In my implementation, if I try to read from stack[v] and if it is empty, then I replace that phi argument with "undefined" to indicate the variable doesn't exist along the path. One example of undefined values being tricky is that in my first implementation I initialized the stack to be empty, but caused the ssa form of argwrite.bril to be wrong:
I realized that I needed to initialize the stack to contain only the function arguments (each arg maps to a stack containing the same arg name). Another issue I had was a small bug in my l5 implementation for computing the dominance frontier: when checking that A does not strictly dominate B, I forgot to add the case for when A is equal to B (I'm not sure how I missed this- unfortunately my code to check the correctness of the dominance frontier was wrong for the same reason). This bug resulted in a phi node being missing in the case of self loops. From SSAConverting out of SSA was pretty straightforward. My only issue was that I tried to add id instructions as the second-to-last instruction in every block, but this meant that some arguments could be undefined if the last instruction wasn't a jump or a brach. Then I remembered there is a TestingI used the |
Beta Was this translation helpful? Give feedback.
-
More than a month late but my implementation is finally here! UsageTo convert a bril program to SSA form, run
To convert a SSA bril program to a non-SSA bril program, run
ImplementationAs the writeup says, conversion to SSA was the most difficult part! The challenges were mostly in the details, such as:
So a lot of the implementation process was focused on trying to fix small bugs. This process was really helpful in cementing my understanding of dominators as I went back to the drawing board a couple of times to determine if my dominator computing functions were correct in the first place! The conversion from SSA wasn't too bad, besides having to deal with cases where the blocks ended with TestingI tested by running my implementation of |
Beta Was this translation helpful? Give feedback.
-
Sorry for the late post, but here is my implementation. UsageTo SSA
From SSA
ImplementationIt's indeed more difficult than what I've originally thought. I have spent quite a while debugging my code. Partly because my code was a bit hacky and I missed some of the details which turned out to be crucial for the correctness. Here is some notable mistakes I made:
Some of the above mistakes are seemingly trivial, but it actually took me a while to debug since I didn't expect myself to make these mistakes. TestingI have run all the tests in |
Beta Was this translation helpful? Give feedback.
-
Holy crap. I'm kind of on a dopamine high right now, so I haven't done the out-of-SSA transformation, but I'm super excited about the algorithm I came up with, so I just want to document it right now. There were a TON of bugs with the SSA implementation, it really made me discouraged and skeptical that the program was working. A lot of misinterpretations of the pseudocode, and one thing lead to another, and I was pretty fed up with this lesson. I skipped ahead to lesson 7, only to feel bad and get even more lost, so in total lesson 5+6 basically took over a month to complete lol. But it was all worth it in the end, since I got my algorithm to work! ContextTake the following program:
The issue with the in-class algorithm is that it says, "For all places where this variable is defined, check its domination frontier". Well, i isn't defined in b2, but the only block that has a domination frontier is b2. So you'll never get it. So the straightforward interpretation of "defs", which is just mark any block where that variable has an assignment, doesn't work. I assumed that the examples/to_ssa.py program did some kind of dataflow analysis in this case, or just said screw it and checked every node that was part of a domination frontier. So i tried both approaches (and a lot more), and ended up giving up for about a week before I somehow just got divine inspiration. The idea is to literally just simulate phi nodes with dataflow analysis - every assignment to a variable, you can think of as a copy of that variable originating from that block. Then, if a block has 2+ different block copies of that variable going into it, assume that a phi node is going to go there, and the outblock will have a copy of that variable originating from that block. Otherwise, if it only has 1 block copy going into it, don't alter the block copy at all, and let it pass through normally. I'm pretty confident in my program, because I ran it against all the benchmarks and it finally passed - those benchmarks have some nasty edge cases. I'm also pretty confident in my algorithm, although admittedly the merging at in-nodes operation is pretty sketchy, I have an intuitive argument but formally idk how to prove. The functions applied in the transfer function and out-nodes are (I think I proved correctly) monotonic though so that's fine. The rest I just copied from the pseudocode provided in class, it's pretty neat that the recursive naming algorithm just works. I know this is different from the examples/to_ssa.py program, because when I was comparing outputs to see where I was going wrong, I noticed that examples/to_ssa.py had a lot of unnecessary phi nodes, with a bunch of undefineds or nodes where the variable is constant throughout the program. For example, in that very same program, the examples/to_ssa.py program would throw in a phi node for the cond variable even when it's not needed. BenchmarksFirst, after a ton of hassle, I confirmed that on all the benchmarks and my small tests, my program is indeed in SSA form and that it doesn't affect program behavior. So it's correct (on our small samples) at the very least. Then, I ran a dynamic instructions benchmark to see just how much time I saved. Surprisingly, a ton! For example, on eight-queens.bril, I had 110k instructions as opposed to 184k instructions. That's a 40% speedup right there! On others, not so much - for fizbuzz, I had 60 million instructions as opposed to 63 million instructions. And for ackermann, I had literally 1 less instruction, which I thought was pretty funny. Oh, and on my own program, bitwiseops, I saved 12% time, which was nice :) TL;DR it's faster than the provided class implementation. Granted, I really don't know the real correctness of this algorithm - I still barely understand why the pseudocode provided in class works, and it's my first time writing a real dataflow analysis without just copying from an existing example. But given my current tools and effort (and sunk cost fallacy), I'm calling it correct. Hopefully you believe in me too :) Update on the from-ssa program coming soon, I'm pooped for today tbh. A jumbled mess of thoughts can be found on my README and implementation here |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
All reactions