Skip to content

Commit

Permalink
Add LLDB pretty-printing (#8460)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexreinking authored Nov 22, 2024
1 parent 0ff31a1 commit 31335b4
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 48 deletions.
1 change: 1 addition & 0 deletions .lldbinit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
command script import ./tools/lldbhalide.py
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,4 @@ code to Halide:
|------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| [CMake developer](doc/CodeStyleCMake.md) | Guidelines for authoring new CMake code. |
| [FuzzTesting](doc/FuzzTesting.md) | Information about fuzz testing the Halide compiler (rather than pipelines). Intended for internal developers. |
| [Testing](doc/Testing.md) | Information about our test organization and debugging tips. Intended for internal developers. |
125 changes: 125 additions & 0 deletions doc/Testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Testing

Halide uses CTest as its primary test platform and runner.

## Organization

Halide's tests are organized beneath the top-level `test/` directory. These
folders are described below:

| Folder | Description |
|----------------------|----------------------------------------------------------------------------------|
| `autoschedulers/$AS` | Test for the `$AS` (e.g. `adams2019`) autoscheduler |
| `common` | Code that may be shared across multiple tests |
| `correctness` | Tests that check correctness of various compiler properties |
| `error` | Tests that expect an exception to be thrown (or `abort()` to be called) |
| `failing_with_issue` | Correctness tests that are associated with a particular issue on GitHub |
| `fuzz` | Fuzz tests. Read more at [FuzzTesting.md](FuzzTesting.md) |
| `generator` | Tests of Halide's AOT compilation infrastructure. |
| `integration` | Tests of Halide's CMake package for downstream use, including cross compilation. |
| `performance` | Tests that check that certain schedules indeed improve performance. |
| `runtime` | Unit tests for the Halide runtime library |
| `warning` | Tests that expected warnings are indeed issued. |

The tests in each of these directories are given CTest labels corresponding to
the directory name. Thus, one can use `ctest -L generator` to run only the
`generator` tests. The `performance` tests configure CTest to not run them
concurrently with other tests (including each other).

The vast majority of our tests are simple C++ executables that link to Halide,
perform some checks, and print the special line `Success!` upon successful
completion. There are three main exceptions to this:

First, the `warning` tests are expected to print a line that reads
`Warning:` and do not look for `Success!`.

Second, some tests cannot run in all scenarios; for example, a test that
measures CUDA performance requires a CUDA-capable GPU. In these cases, tests are
expected to print `[SKIP]` and exit and not print `Success!` or `Warning:`.

Finally, the `error` tests are expected to throw an (uncaught) exception that is
not a `Halide::InternalError` (i.e. from a failing `internal_assert`). The logic
for translating uncaught exceptions into successful tests is in
`test/common/expect_abort.cpp`.

## Debugging with LLDB

We provide helpers for pretty-printing Halide's IR types in LLDB. The
`.lldbinit` file at the repository root will load automatically if you launch
`lldb` from this directory and your `~/.lldbinit` file contains the line,

```
settings set target.load-cwd-lldbinit true
```

If you prefer to avoid such global configuration, you can directly load the
helpers with the LLDB command,

```
command script import ./tools/lldbhalide.py
```

again assuming that the repository root is your current working directory.

To see the benefit of using these helpers, let us debug `correctness_bounds`:

```
$ lldb ./build/test/correctness/correctness_bounds
(lldb) breakpoint set --file bounds.cpp --line 18
Breakpoint 1: where = correctness_bounds`main + 864 at bounds.cpp:18:12, address = 0x0000000100002054
(lldb) run
Process 29325 launched: '/Users/areinking/dev/Halide/build/test/correctness/correctness_bounds' (arm64)
Defining function...
Process 29325 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100002054 correctness_bounds`main(argc=1, argv=0x000000016fdff160) at bounds.cpp:18:12
15 g(x, y) = min(x, y);
16 h(x, y) = clamp(x + y, 20, 100);
17
-> 18 Var xo("xo"), yo("yo"), xi("xi"), yi("yi");
19
20 Target target = get_jit_target_from_environment();
21 if (target.has_gpu_feature()) {
Target 0: (correctness_bounds) stopped.
(lldb)
```

Now we can try to inspect the Func `h`. Without the helpers, we see:

```
(lldb) v h
(Halide::Func) {
func = {
contents = {
strong = (ptr = 0x0000600002486a20)
weak = nullptr
idx = 0
}
}
pipeline_ = {
contents = (ptr = 0x0000000000000000)
}
}
```

But if we load the helpers and try again, we get a much more useful output:

```
(lldb) command script import ./tools/lldbhalide.py
(lldb) v h
... lots of output ...
```

The amount of output here is maybe a bit _too_ much, but we gain the ability to
more narrowly inspect data about the func:

```
(lldb) v h.func.init_def.values
...
(std::vector<Halide::Expr>) h.func.init_def.values = size=1 {
[0] = max(min(x + y, 100), 20)
}
```

These helpers are particularly useful when using graphical debuggers, such as
the one found in CLion.
136 changes: 88 additions & 48 deletions src/IRPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,11 @@ void IRPrinter::print(const Stmt &ir) {
ir.accept(this);
}

void IRPrinter::print_summary(const Stmt &ir) {
ScopedValue<bool> old(is_summary, true);
ir.accept(this);
}

void IRPrinter::print_list(const std::vector<Expr> &exprs) {
for (size_t i = 0; i < exprs.size(); i++) {
print_no_parens(exprs[i]);
Expand Down Expand Up @@ -865,7 +870,9 @@ void IRPrinter::visit(const Let *op) {
stream << "let " << op->name << " = ";
print(op->value);
stream << " in ";
print(op->body);
if (!is_summary) {
print(op->body);
}
close();
}

Expand All @@ -875,7 +882,9 @@ void IRPrinter::visit(const LetStmt *op) {
print_no_parens(op->value);
stream << "\n";

print(op->body);
if (!is_summary) {
print(op->body);
}
}

void IRPrinter::visit(const AssertStmt *op) {
Expand Down Expand Up @@ -905,25 +914,18 @@ void IRPrinter::visit(const For *op) {
print_no_parens(op->min);
stream << ", ";
print_no_parens(op->extent);
stream << ") {\n";
stream << ") ";

indent++;
print(op->body);
indent--;

stream << get_indent() << "}\n";
print_braced_stmt(op->body, 1);
}

void IRPrinter::visit(const Acquire *op) {
stream << get_indent() << "acquire (";
print_no_parens(op->semaphore);
stream << ", ";
print_no_parens(op->count);
stream << ") {\n";
indent++;
print(op->body);
indent--;
stream << get_indent() << "}\n";
stream << ") ";
print_braced_stmt(op->body, 1);
}

void IRPrinter::print_lets(const Let *let) {
Expand All @@ -932,7 +934,9 @@ void IRPrinter::print_lets(const Let *let) {
stream << "let " << let->name << " = ";
print_no_parens(let->value);
stream << " in\n";
if (const Let *next = let->body.as<Let>()) {
if (is_summary) {
stream << get_indent() << "...\n";
} else if (const Let *next = let->body.as<Let>()) {
print_lets(next);
} else {
stream << get_indent();
Expand All @@ -941,6 +945,19 @@ void IRPrinter::print_lets(const Let *let) {
}
}

void IRPrinter::print_braced_stmt(const Stmt &stmt, int extra_indent) {
if (is_summary) {
stream << "{ ... }\n";
return;
}

stream << "{\n";
indent += extra_indent;
print(stmt);
indent -= extra_indent;
stream << get_indent() << "}\n";
}

void IRPrinter::visit(const Store *op) {
stream << get_indent();
const bool has_pred = !is_const_one(op->predicate);
Expand Down Expand Up @@ -1038,7 +1055,10 @@ void IRPrinter::visit(const Allocate *op) {
stream << get_indent() << " custom_delete { " << op->free_function << "(" << op->name << "); }";
}
stream << "\n";
print(op->body);

if (!is_summary) {
print(op->body);
}
}

void IRPrinter::visit(const Free *op) {
Expand Down Expand Up @@ -1067,13 +1087,9 @@ void IRPrinter::visit(const Realize *op) {
stream << " if ";
print(op->condition);
}
stream << " {\n";

indent++;
print(op->body);
indent--;

stream << get_indent() << "}\n";
stream << " ";
print_braced_stmt(op->body);
}

void IRPrinter::visit(const Prefetch *op) {
Expand Down Expand Up @@ -1102,12 +1118,16 @@ void IRPrinter::visit(const Prefetch *op) {
indent--;
stream << get_indent() << "}\n";
}
print(op->body);
if (!is_summary) {
print(op->body);
}
}

void IRPrinter::visit(const Block *op) {
print(op->first);
print(op->rest);
if (!is_summary) {
print(op->first);
print(op->rest);
}
}

void IRPrinter::visit(const Fork *op) {
Expand All @@ -1121,14 +1141,23 @@ void IRPrinter::visit(const Fork *op) {
stmts.push_back(rest);

stream << get_indent() << "fork ";
for (const Stmt &s : stmts) {
stream << "{\n";
indent++;
print(s);
indent--;
stream << get_indent() << "} ";
if (is_summary) {
stream << "[" << stmts.size();
if (stmts.size() == 1) {
stream << " child]";
} else {
stream << " children]";
}
} else {
for (const Stmt &s : stmts) {
stream << "{\n";
indent++;
print(s);
indent--;
stream << get_indent() << "} ";
}
stream << "\n";
}
stream << "\n";
}

void IRPrinter::visit(const IfThenElse *op) {
Expand Down Expand Up @@ -1209,32 +1238,43 @@ void IRPrinter::visit(const VectorReduce *op) {
}

void IRPrinter::visit(const Atomic *op) {
stream << get_indent();

if (op->mutex_name.empty()) {
stream << get_indent() << "atomic ("
<< op->producer_name << ") {\n";
stream << "atomic (" << op->producer_name << ") ";
} else {
stream << get_indent() << "atomic ("
<< op->producer_name << ", "
<< op->mutex_name << ") {\n";
stream << "atomic (" << op->producer_name << ", " << op->mutex_name << ") ";
}
indent += 2;
print(op->body);
indent -= 2;
stream << get_indent() << "}\n";

print_braced_stmt(op->body);
}

void IRPrinter::visit(const HoistedStorage *op) {
if (op->name.empty()) {
stream << get_indent() << "hoisted_storage {\n";
stream << get_indent() << "hoisted_storage ";
} else {
stream << get_indent() << "hoisted_storage (";
stream << op->name;
stream << ") {\n";
stream << get_indent() << "hoisted_storage (" << op->name << ") ";
}
indent += 2;
print(op->body);
indent -= 2;
stream << get_indent() << "}\n";

print_braced_stmt(op->body);
}

std::string lldb_string(const Expr &ir) {
std::stringstream s{};
IRPrinter p(s);
p.print_no_parens(ir);
return s.str();
}

std::string lldb_string(const Internal::BaseExprNode *n) {
return lldb_string(Expr(n));
}

std::string lldb_string(const Stmt &ir) {
std::stringstream s{};
IRPrinter p(s);
p.print_summary(ir);
return s.str();
}

} // namespace Internal
Expand Down
Loading

0 comments on commit 31335b4

Please sign in to comment.