Skip to content
jrk edited this page Sep 13, 2014 · 19 revisions

Debugging inferred function bounds

HL_TRACE=1 prints out the boundaries of all realizations of Funcs. (Inlined Funcs don't really have boundaries.)

Higher values of HL_TRACE start to print the loads and stores too.

You can also call Func::trace_realizations to get a printf that displays the boundaries for a particular func.

Inspecting the value of an Expr at runtime

You can wrap a print around an Expr to print its value at runtime, e.g say you think b is taking on bad values in the following Func definition:

f(x) = a + b;

You could rewrite this to:

f(x) = a + print(b, " <- b has this value when x is ", x);

print returns the first argument, and prints all the arguments as a side-effect. The arguments may be of type Expr, std::string, const char *, int, or float.

You can also conditionally print:

f(x) = a + print_when(b < 0, b, " b was not supposed to be negative! It happened at x = ", x);

print_when returns the second argument. When the first argument is true, it prints all the arguments as a side-effect.

Understanding a schedule

You can use Func::compile_to_lowered_stmt to compile a Halide pipeline to human-readable imperative psuedo-code that includes all the effects of scheduling. E.g:

#include <Halide.h>

using namespace Halide;

int main(int argc, char **argv) {
    Func f;
    Var x, y;
    f(x, y) = x+y;
    f.vectorize(x, 4).unroll(x, 2).parallel(y);
    f.compile_to_lowered_stmt("f.html", HTML);

    return 0;
}

produces this output

This functionality also has a variant which simplifies the lowered pseudocode, given concrete output bounds and other parameter values, called Func::compile_to_simplified_lowered_stmt. This does not reflect the exact code which is generated during compilation, which normally supports arbitrary output bounds and parameter sizes, but it can further simplify the basic structure of the pseudocode for easier comprehension. It is used just like the example above, but with additional arguments to specify concrete parameter values for simplification:

//...
// dump simplified pseudocode, assuming a 256x224 output, like f.realize(256, 224)
f.compile_to_simplified_lowered_stmt("f-simplified.html", 256, 224, HTML);

// or, given a function with more parameters...
Param<int> p;
Func g;
g(x,y) = f(p*x, p*y);

// ...specify concrete values for those parameters, as well:
std::map<std::string, Expr> params;
params[p.name()] = 2;
g.compile_to_simplified_lowered_stmt("g-simplified.html", 256, 224, params, HTML);

// and the full program, for comparison:
g.compile_to_lowered_stmt("g.html", HTML);

The simplified version of f has extraneous checks removed and significantly simplified index and bounds expressions. The contrast between simplified and complete versions of the multi-stage pipeline up to g is even more pronounced.