Assignment 4: Finding Redundant Null Checks in JoeQ

When generating quads out of Java bytecodes, safety checks are explicitly inserted into the control flow graph. In particular, every register is NULL_CHECKed before it is deferenced. These safety checks are necessary to ensure memory safety, but cause substantial runtime overhead. In this assignment, you will design sound optimizations to eliminate redundant checks and to optimize the program in general.

The optimize package contains a monotone dataflow framework, several client analyses, and additional utilies that may be useful. You may only modify classes in the optimize.* package except for optimize.OptimizeHarness. In addition, you must adhere to the interfaces of the methods optimize.Optimize.optimize and optimize.FindRedundantNullChecks.main as specified in the comments preceding each method declaration.

We have setup an environment for the programming assignments found here, very similar to homework 2. Download and unpack this archive on your own machine, or in your home directory on a Stanford machine like myth (you can use elaine, but don't test on it: its gcj produces different bytecode for the test cases).

Code Framework

Download it here, Requires Java 5

JoeQ Tutorial

JoeQ Javadoc

Framework Contents

The top level directory structure of the optimize environment is:

optimize/ Root optimize environment for our JoeQ distribution
optimize/bin/ Contains the optimize scripts to set up the environment and launch analyses
optimize/lib/ Contains the Joeq JAR. Unzip the JAR to see the JoeQ source.
optimize/src/ Contains examples, as well as stubs for your code
optimize/Makefile Compiles all the src .java files into a jar, ready to be run using the optimize script

The src directory contains the source files that you'll be using and creating for this programming assignment. This includes the following subdirectories/packages:

/src/optimize/ Contains example code and stubs for your assignment
/src/optimize/FindRedundantNullChecks.java Fill this in with analyses to find redundant null checks
/src/optimize/Optimize.java Fill this in with transformations to optimize the given classes for extra credit
/src/optimize/test/ Contains the programs you will find null checks in, and the correct answers against which you can test your code.

Your Task

Write an analysis to find all redundant NULL_CHECKs. A NULL_CHECK on register x is redundant at point p if x successfully passed a NULL_CHECK along all possible paths to p. For example, in the following code, your analysis must find quad 5 to be redundant but does not need to find any other quad to be redundant.

1   MOVE T1 String, T0 String
2   NULL_CHECK T-1 , T1 String
3   MOVE T2 String, T1 String
4   NULL_CHECK T-1 , T0 String
5   NULL_CHECK T-1 , T1 String
6   NULL_CHECK T-1 , T2 String

In other words, your analysis needs to find that a NULL_CHECK is redundant on a register only if that particular register was NULL_CHECK'ed along all possible paths to that NULL_CHECK. The analysis does not have to reason about copies of values to or from other previously or subsequently NULL_CHECK'ed registers.

The optimize.FindRedundantNullChecks.main(String[]) method takes an array of names of classes that should be analyzed for redundant null checks. Fill in the optimize.FindRedundantNullChecks.main(String[]) method so that it prints exactly one line for each method that contains the method name and a subset of the quad ids of redundant NULL_CHECKs. For example:

myth1:~/optimize$ bin/parun optimize.FindRedundantNullChecks optimize.test.SomeTest
main 4 17 19
sample 5
<init>

means that NULL_CHECKs with quad ids 4, 17, and 19 are redundant in main, quad ids 5 are redundant in sample, and no quads are redundant in <init>.

The optimize.test package contains two test classes named optimize.test.NullTest and optimize.test.SkipList (optimize.test.QuickSort is not used for redundant null checks). The outputs that should be generated by running optimize.FindRedundantNullChecks.main(String[]) over these two classes are in src/optimize/test/NullTest.basic.out and src/optimize/test/SkipList.basic.out.

Extra Credit

Perform any other sound optimization that speeds up the optimize.test.SkipList program (the skip list implementation is from here) and optimize.test.QuickSort program. The extra credit points awarded will range from 0 to 100. The number of points will depend on the number of quads executed by the optimized program, and will be applied after all grades are curved. If you work in a group of two, the same extra credit score is assigned to both members. All optimizations must be sound! For example, if you remove even one necessary null or bounds check, or falsely copy a constant, you will receive no extra credit.

Your extra credit optimizations must transform the code so that the grading script can interpret the optimized program to determine the number of quads actually executed. The two easiest transforms are the removal and addition of quads using the QuadIterator's remove() and add(Quad q) methods which will remove the most recently returned quad or insert a quad after it, respectively. To change the values of Operands, the Operator class contains static methods to set the appropriate argument. For example, Move has methods setDest and setSrc. To modify the ControlFlowGraph, use ControlFlowGraph.createBasicBlock to construct a BasicBlock and use the add methods in BasicBlock to modify the lists of quads. See the joeq javadocs for details.

The optimize.OptimizeHarness.main(String[]) method takes an list of names of classes that should be optimized, a run class that contains a static main(String[]) method, and a list of run parameters to be passed to the main method. For example:

myth1:~/optimize$ bin/parun optimize.OptimizeHarness --optimize optimize.test.SkipList --run-main optimize.test.SkipList --run-param 20
14 6 21 ...  28 14 17
Result of interpretation: Returned: null (null checks: 29376 quad count: 111014)

applies your optimizations to optimize.test.SkipList, and then interprets optimize.test.SkipList with parameter 20. The interpreter prints out the number of quads executed.

The following is an example output of optimize.test.QuickSort:

myth1:~/optimize$ bin/parun optimize.OptimizeHarness --optimize optimize.test.QuickSort --run-main optimize.test.QuickSort --run-param 200
10 18 20 ... 2838 2844 2878
Result of interpretation: Returned: null (null checks: 35263 quad count: 139456)

The optimize.OptimizeHarness.main(String[]) method invokes the optimize.Optimize.optimize(List<String>) method which should load the classes to be optimized and apply the control flow graph transformations. The transformed control flow graphs should automatically be stored by joeq.Compiler.Quad.CodeCache. Read joeq.Compiler.Quad.CodeCache and joeq.Main.Helper carefully to understand how control flow graphs are cached. The optimize.OptimizeHarness.main(String[]) method then interprets the run class with respect to the list of run parameters using the CodeCacheed control flow graphs.

Fill in the optimize.Optimize.optimize(List<String>) method so that it applies your optimizations to the classes named by the list of strings parameter.

Describe the design of your extra credit optimizations in the design.txt file in the src/optimize directory.

Submission

We ONLY want the java files in your src/optimize directory, do not submit the rest of the framework.

To submit your assignment, please follow the following steps:

  1. Copy your hw4's src/optimize directory to myth.stanford.edu (keep it in it's own directory)
  2. Login to myth.stanford.edu
  3. cd to the directory containing only the java files you want to submit
  4. run /usr/class/cs243/optimize/submit_hw4.sh in this directory.
    OR, if you're working with a partner, type:
    /usr/class/cs243/optimize/submit_hw4.sh partner_SUID
  5. (ex: ./submit_hw4.sh jongsoo)

Only one submission is required per pair.

If you discover a bug after submitting (and before the due date), simply run the submission script again. We'll take the latest version.

Hints

Again, get started early. First, think about what are the biggest optimization opportunities. No matter how sophisticated your optimization is, if the maximum speedup from that is 1%, spending time on this is probably not worth it (Remember Amdahl's law). To do that, it may be a good idea to look at the code of SkipList and QuickSort (both Java source code and Quad representation generated by Joeq).

Compared to HW2 and the first part of HW4, you need to transform your code instead of just doing some analyses. Transforming code (especially, if you want to modify control flows) is much trickier and may involve more Joeq Javadoc reading.