Debugging - CSUS-LLVM/OptSched GitHub Wiki


Debugging a Mismatch

So you've found that validation-test.py or plaidbench-validation-test.py reports a mismatch between two of your runs. How do we approach debugging this mismatch?

The first thing to verify is whether we should expect the runs to be mismatch-free. Mismatch checking only works if the costs between the runs are comparable. It's expected to have mismatches if you change the cost function, e.g. from PERP to SLIL.

Minimize the test case

If you discovered the mismatch from compiling all of SPEC CPU2006, choose a single benchmark which has a mismatch and only run that. Ideally, you'd choose a smaller benchmark rather than a larger one.

To determine which benchmark a block comes from, spills.dat from the output of runspec-wrapper-optsched.py shows the function names, making it possible to search that file for the given block (minus the :##; block names are functionName:#blkNumber#, e.g. foo:23).

A script to do this search for you can be found in util/misc/findblock.py.

Visualize the DDG

It can be very helpful to visualize the DDG for a small mismatch case, presuming there is a small mismatch case.

Dumping a DDG

  • Set DUMP_DDGS to YES
  • Set DDG_DUMP_PATH to the directory where you wish to have the files written

You probably also want to set SCHEDULE_SPECIFIC_REGIONS to YES and REGIONS_TO_SCHEDULE to the blocks of interest. Then, run the scheduler over as small of a benchmark as possible (to take minimal time). util/misc/findblock.py may be helpful for finding the benchmark associated with a block.

Visualizing the DDG

Convert the DDG to a graphical format. Graphviz has dot, which may be desirable. util/misc/ddg2dot.py will convert our WriteToFile .ddg format to dot syntax. Running dot on the output gives a graph visualization.

Visualization of Graph Transformations

Some small things can make it easier to see the change that the Graph Transformation did. In short, write the DDG before and after the transformations, then proceed as above for each file. However, the Graphviz output dags will likely differ in layout.

To make the layout the same, meaning that the only difference is the added edges, copy the added edges over to the .dot file without the edges, adding style=invis. For example:

Before transformations:

digraph G {
    n0 [label="LEA64r:n0"];
    n1 [label="LEA64r:n1"];
    n2 [label="COPY:n2"];
    n3 [label="entry:n3"];
    n4 [label="exit:n4"];

    n0 -> n4 [label="other"];
    n1 -> n4 [label="other"];
    n2 -> n4 [label="other"];
    n3 -> n2 [label="other"];
    n3 -> n1 [label="other"];
    n3 -> n0 [label="other"];
}

After transformations:

digraph G {
    n0 [label="LEA64r:n0"];
    n1 [label="LEA64r:n1"];
    n2 [label="COPY:n2"];
    n3 [label="entry:n3"];
    n4 [label="exit:n4"];

    n0 -> n4 [label="other"];
    n0 -> n2 [label="other"];
    n0 -> n1 [label="other"];
    n1 -> n4 [label="other"];
    n1 -> n2 [label="other"];
    n2 -> n4 [label="other"];
    n3 -> n2 [label="other"];
    n3 -> n1 [label="other"];
    n3 -> n0 [label="other"];
}

Altered before to remove layout differences:

digraph G {
    n0 [label="LEA64r:n0"];
    n1 [label="LEA64r:n1"];
    n2 [label="COPY:n2"];
    n3 [label="entry:n3"];
    n4 [label="exit:n4"];

    n0 -> n4 [label="other"];
    n0 -> n2 [label="other" style=invis];
    n0 -> n1 [label="other" style=invis];
    n1 -> n4 [label="other"];
    n1 -> n2 [label="other" style=invis];
    n2 -> n4 [label="other"];
    n3 -> n2 [label="other"];
    n3 -> n1 [label="other"];
    n3 -> n0 [label="other"];
}

Dump Use/Defs

It can be very useful to have Use/Def information for the block, as well as some other debug info. To obtain this, build LLVM and OptSched in Debug configuration, or pass -DLLVM_ENABLE_ASSERTIONS=ON when configuring CMake in release mode. This enables the -debug and -debug-only=optsched-ddg-wrapper commandline options, which dump the info.

To add these flags to the SPEC benchmarks, change your .cfg file to pass the flags to the compiler.

Valgrind

Run the single compilation command with valgrind. E.g., for the SPEC benchmarks, the tools output the compilation command used in the log file, so:

  1. Go to the build directory for the particular benchmark inside the SPEC suite.
  2. Run valgrind --trace-children=yes <compilation-command>.
  3. Optional. To detect memory issues such as leaks, use --tool=memcheck. To profile heap / memory usage, use --tool=massif.

GDB

Do the same as in the Valgrind section to find the particular compilation command. Then, use GDB against the compiler in question (e.g. clang). In the GDB UI, you can specify the arguments passed to clang (e.g. the OptSched plugin and source code files being compiled), or, alternatively, you can specify these in the original call to GDB via --args. Finally, ensure that GDB debugs the dynamically loaded OptSched by setting follow-fork-mode to child. Example usage:

  1. gdb /path/to/compiler/build/bin/compilerExecutable
  2. set args <compilation-command-args>
  3. set follow-fork-mode child
  4. run

Note, since OptSched is dynamically loaded, we need to code a breakpoint into the source code. For x86 ISA, include __asm__ __volatile__("int $3"); in the source code at desired location. Other ISAs may require different assembly instructions (e.g. the instruction for breakpoint in PTX ISA is "brkpt"). (GDB docs: https://sourceware.org/gdb/onlinedocs/gdb/Forks.html ).