Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LLDB pretty-printing #8460

Merged
merged 6 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading