The code I wrote for this question implements a trivial dead code elimination (DCE) optimization for Bril programs. It works by iterating through the instructions in reverse order, maintaining a set of used variables. Instructions that define variables not in this set are eliminated. The algorithm adds variables used as arguments to the set and removes variables when their defining instruction is processed. This approach ensures that instructions defining unused variables are removed while preserving the program’s essential structure and functionality.
As we can see in above output, line 3 ‘c: int = const 1;’ is removed because c is assigned and never used. we can also further test the code with a bigger input:
As we expect, we can see that line 3 ise removed, because c is assigned and used in line 5 for defining e but e is never used so these two lines are both dead code, also a is reassigned in line 6 and thus removed.
Challenges Faced
One of the challenges that I can think of in this question, is that for large and complex programs, the reverse iteration and set operations might become a performance bottleneck. Addressing these challenges would involve extending the algorithm, careful consideration of Bril’s semantics, and developing more sophisticated testing strategies.
Part 2
Explanation of the Code
The lvn code implements Local Value Numbering to optimize Bril programs by eliminating redundant computations within basic blocks. It processes each instruction, assigns value numbers to expressions based on their operations and operands, and maintains tables to track these values. When it encounters a redundant computation, it eliminates it by reusing the previously computed result, effectively reducing the number of instructions and optimizing the code.
As we can see in the output above, line 5 has been changed to prod: int = mul sum1 sum1;
Testing the Code
For testing the correctness of the code, I used brench.py code in bril, and for that we need to have a brench.toml file (or a configuration file for brench.py) configures the Brench tool to run a Bril benchmark through four different pipelines: baseline (no optimization), DCE only, LVN only, and LVN followed by DCE. It then extracts the total number of dynamic instructions executed for each run, allowing us to compare the effectiveness of these optimizations. We use brench.toml with python3 ../brench/brench.py brench.toml > results.csv
Based on this analysis, let’s count the instructions that should remain after each optimization:
Baseline: 15 instructions (all original instructions)
DCE: 13 instructions (removes h and i)
LVN: 11 instructions (keeps a, b, c, f, prod, and the three print statements)
LVN + DCE: 9 instructions (same as LVN, but also removes h and i)
Therefore, We can confirm the correctness of the implementations.
Challenges
The main challenges in lvn.py include handling commutative operations (like recognizing that add a b and add b a are equivalent) and ensuring that value numbering accurately tracks and replaces redundant computations without altering the program’s correct behavior.