Skip to content

Commit 31335b4

Browse files
authored
Add LLDB pretty-printing (#8460)
1 parent 0ff31a1 commit 31335b4

File tree

6 files changed

+335
-48
lines changed

6 files changed

+335
-48
lines changed

.lldbinit

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
command script import ./tools/lldbhalide.py

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,4 @@ code to Halide:
436436
|------------------------------------------|---------------------------------------------------------------------------------------------------------------|
437437
| [CMake developer](doc/CodeStyleCMake.md) | Guidelines for authoring new CMake code. |
438438
| [FuzzTesting](doc/FuzzTesting.md) | Information about fuzz testing the Halide compiler (rather than pipelines). Intended for internal developers. |
439+
| [Testing](doc/Testing.md) | Information about our test organization and debugging tips. Intended for internal developers. |

doc/Testing.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Testing
2+
3+
Halide uses CTest as its primary test platform and runner.
4+
5+
## Organization
6+
7+
Halide's tests are organized beneath the top-level `test/` directory. These
8+
folders are described below:
9+
10+
| Folder | Description |
11+
|----------------------|----------------------------------------------------------------------------------|
12+
| `autoschedulers/$AS` | Test for the `$AS` (e.g. `adams2019`) autoscheduler |
13+
| `common` | Code that may be shared across multiple tests |
14+
| `correctness` | Tests that check correctness of various compiler properties |
15+
| `error` | Tests that expect an exception to be thrown (or `abort()` to be called) |
16+
| `failing_with_issue` | Correctness tests that are associated with a particular issue on GitHub |
17+
| `fuzz` | Fuzz tests. Read more at [FuzzTesting.md](FuzzTesting.md) |
18+
| `generator` | Tests of Halide's AOT compilation infrastructure. |
19+
| `integration` | Tests of Halide's CMake package for downstream use, including cross compilation. |
20+
| `performance` | Tests that check that certain schedules indeed improve performance. |
21+
| `runtime` | Unit tests for the Halide runtime library |
22+
| `warning` | Tests that expected warnings are indeed issued. |
23+
24+
The tests in each of these directories are given CTest labels corresponding to
25+
the directory name. Thus, one can use `ctest -L generator` to run only the
26+
`generator` tests. The `performance` tests configure CTest to not run them
27+
concurrently with other tests (including each other).
28+
29+
The vast majority of our tests are simple C++ executables that link to Halide,
30+
perform some checks, and print the special line `Success!` upon successful
31+
completion. There are three main exceptions to this:
32+
33+
First, the `warning` tests are expected to print a line that reads
34+
`Warning:` and do not look for `Success!`.
35+
36+
Second, some tests cannot run in all scenarios; for example, a test that
37+
measures CUDA performance requires a CUDA-capable GPU. In these cases, tests are
38+
expected to print `[SKIP]` and exit and not print `Success!` or `Warning:`.
39+
40+
Finally, the `error` tests are expected to throw an (uncaught) exception that is
41+
not a `Halide::InternalError` (i.e. from a failing `internal_assert`). The logic
42+
for translating uncaught exceptions into successful tests is in
43+
`test/common/expect_abort.cpp`.
44+
45+
## Debugging with LLDB
46+
47+
We provide helpers for pretty-printing Halide's IR types in LLDB. The
48+
`.lldbinit` file at the repository root will load automatically if you launch
49+
`lldb` from this directory and your `~/.lldbinit` file contains the line,
50+
51+
```
52+
settings set target.load-cwd-lldbinit true
53+
```
54+
55+
If you prefer to avoid such global configuration, you can directly load the
56+
helpers with the LLDB command,
57+
58+
```
59+
command script import ./tools/lldbhalide.py
60+
```
61+
62+
again assuming that the repository root is your current working directory.
63+
64+
To see the benefit of using these helpers, let us debug `correctness_bounds`:
65+
66+
```
67+
$ lldb ./build/test/correctness/correctness_bounds
68+
(lldb) breakpoint set --file bounds.cpp --line 18
69+
Breakpoint 1: where = correctness_bounds`main + 864 at bounds.cpp:18:12, address = 0x0000000100002054
70+
(lldb) run
71+
Process 29325 launched: '/Users/areinking/dev/Halide/build/test/correctness/correctness_bounds' (arm64)
72+
Defining function...
73+
Process 29325 stopped
74+
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
75+
frame #0: 0x0000000100002054 correctness_bounds`main(argc=1, argv=0x000000016fdff160) at bounds.cpp:18:12
76+
15 g(x, y) = min(x, y);
77+
16 h(x, y) = clamp(x + y, 20, 100);
78+
17
79+
-> 18 Var xo("xo"), yo("yo"), xi("xi"), yi("yi");
80+
19
81+
20 Target target = get_jit_target_from_environment();
82+
21 if (target.has_gpu_feature()) {
83+
Target 0: (correctness_bounds) stopped.
84+
(lldb)
85+
```
86+
87+
Now we can try to inspect the Func `h`. Without the helpers, we see:
88+
89+
```
90+
(lldb) v h
91+
(Halide::Func) {
92+
func = {
93+
contents = {
94+
strong = (ptr = 0x0000600002486a20)
95+
weak = nullptr
96+
idx = 0
97+
}
98+
}
99+
pipeline_ = {
100+
contents = (ptr = 0x0000000000000000)
101+
}
102+
}
103+
```
104+
105+
But if we load the helpers and try again, we get a much more useful output:
106+
107+
```
108+
(lldb) command script import ./tools/lldbhalide.py
109+
(lldb) v h
110+
... lots of output ...
111+
```
112+
113+
The amount of output here is maybe a bit _too_ much, but we gain the ability to
114+
more narrowly inspect data about the func:
115+
116+
```
117+
(lldb) v h.func.init_def.values
118+
...
119+
(std::vector<Halide::Expr>) h.func.init_def.values = size=1 {
120+
[0] = max(min(x + y, 100), 20)
121+
}
122+
```
123+
124+
These helpers are particularly useful when using graphical debuggers, such as
125+
the one found in CLion.

src/IRPrinter.cpp

+88-48
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,11 @@ void IRPrinter::print(const Stmt &ir) {
567567
ir.accept(this);
568568
}
569569

570+
void IRPrinter::print_summary(const Stmt &ir) {
571+
ScopedValue<bool> old(is_summary, true);
572+
ir.accept(this);
573+
}
574+
570575
void IRPrinter::print_list(const std::vector<Expr> &exprs) {
571576
for (size_t i = 0; i < exprs.size(); i++) {
572577
print_no_parens(exprs[i]);
@@ -865,7 +870,9 @@ void IRPrinter::visit(const Let *op) {
865870
stream << "let " << op->name << " = ";
866871
print(op->value);
867872
stream << " in ";
868-
print(op->body);
873+
if (!is_summary) {
874+
print(op->body);
875+
}
869876
close();
870877
}
871878

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

878-
print(op->body);
885+
if (!is_summary) {
886+
print(op->body);
887+
}
879888
}
880889

881890
void IRPrinter::visit(const AssertStmt *op) {
@@ -905,25 +914,18 @@ void IRPrinter::visit(const For *op) {
905914
print_no_parens(op->min);
906915
stream << ", ";
907916
print_no_parens(op->extent);
908-
stream << ") {\n";
917+
stream << ") ";
909918

910-
indent++;
911-
print(op->body);
912-
indent--;
913-
914-
stream << get_indent() << "}\n";
919+
print_braced_stmt(op->body, 1);
915920
}
916921

917922
void IRPrinter::visit(const Acquire *op) {
918923
stream << get_indent() << "acquire (";
919924
print_no_parens(op->semaphore);
920925
stream << ", ";
921926
print_no_parens(op->count);
922-
stream << ") {\n";
923-
indent++;
924-
print(op->body);
925-
indent--;
926-
stream << get_indent() << "}\n";
927+
stream << ") ";
928+
print_braced_stmt(op->body, 1);
927929
}
928930

929931
void IRPrinter::print_lets(const Let *let) {
@@ -932,7 +934,9 @@ void IRPrinter::print_lets(const Let *let) {
932934
stream << "let " << let->name << " = ";
933935
print_no_parens(let->value);
934936
stream << " in\n";
935-
if (const Let *next = let->body.as<Let>()) {
937+
if (is_summary) {
938+
stream << get_indent() << "...\n";
939+
} else if (const Let *next = let->body.as<Let>()) {
936940
print_lets(next);
937941
} else {
938942
stream << get_indent();
@@ -941,6 +945,19 @@ void IRPrinter::print_lets(const Let *let) {
941945
}
942946
}
943947

948+
void IRPrinter::print_braced_stmt(const Stmt &stmt, int extra_indent) {
949+
if (is_summary) {
950+
stream << "{ ... }\n";
951+
return;
952+
}
953+
954+
stream << "{\n";
955+
indent += extra_indent;
956+
print(stmt);
957+
indent -= extra_indent;
958+
stream << get_indent() << "}\n";
959+
}
960+
944961
void IRPrinter::visit(const Store *op) {
945962
stream << get_indent();
946963
const bool has_pred = !is_const_one(op->predicate);
@@ -1038,7 +1055,10 @@ void IRPrinter::visit(const Allocate *op) {
10381055
stream << get_indent() << " custom_delete { " << op->free_function << "(" << op->name << "); }";
10391056
}
10401057
stream << "\n";
1041-
print(op->body);
1058+
1059+
if (!is_summary) {
1060+
print(op->body);
1061+
}
10421062
}
10431063

10441064
void IRPrinter::visit(const Free *op) {
@@ -1067,13 +1087,9 @@ void IRPrinter::visit(const Realize *op) {
10671087
stream << " if ";
10681088
print(op->condition);
10691089
}
1070-
stream << " {\n";
1071-
1072-
indent++;
1073-
print(op->body);
1074-
indent--;
10751090

1076-
stream << get_indent() << "}\n";
1091+
stream << " ";
1092+
print_braced_stmt(op->body);
10771093
}
10781094

10791095
void IRPrinter::visit(const Prefetch *op) {
@@ -1102,12 +1118,16 @@ void IRPrinter::visit(const Prefetch *op) {
11021118
indent--;
11031119
stream << get_indent() << "}\n";
11041120
}
1105-
print(op->body);
1121+
if (!is_summary) {
1122+
print(op->body);
1123+
}
11061124
}
11071125

11081126
void IRPrinter::visit(const Block *op) {
1109-
print(op->first);
1110-
print(op->rest);
1127+
if (!is_summary) {
1128+
print(op->first);
1129+
print(op->rest);
1130+
}
11111131
}
11121132

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

11231143
stream << get_indent() << "fork ";
1124-
for (const Stmt &s : stmts) {
1125-
stream << "{\n";
1126-
indent++;
1127-
print(s);
1128-
indent--;
1129-
stream << get_indent() << "} ";
1144+
if (is_summary) {
1145+
stream << "[" << stmts.size();
1146+
if (stmts.size() == 1) {
1147+
stream << " child]";
1148+
} else {
1149+
stream << " children]";
1150+
}
1151+
} else {
1152+
for (const Stmt &s : stmts) {
1153+
stream << "{\n";
1154+
indent++;
1155+
print(s);
1156+
indent--;
1157+
stream << get_indent() << "} ";
1158+
}
1159+
stream << "\n";
11301160
}
1131-
stream << "\n";
11321161
}
11331162

11341163
void IRPrinter::visit(const IfThenElse *op) {
@@ -1209,32 +1238,43 @@ void IRPrinter::visit(const VectorReduce *op) {
12091238
}
12101239

12111240
void IRPrinter::visit(const Atomic *op) {
1241+
stream << get_indent();
1242+
12121243
if (op->mutex_name.empty()) {
1213-
stream << get_indent() << "atomic ("
1214-
<< op->producer_name << ") {\n";
1244+
stream << "atomic (" << op->producer_name << ") ";
12151245
} else {
1216-
stream << get_indent() << "atomic ("
1217-
<< op->producer_name << ", "
1218-
<< op->mutex_name << ") {\n";
1246+
stream << "atomic (" << op->producer_name << ", " << op->mutex_name << ") ";
12191247
}
1220-
indent += 2;
1221-
print(op->body);
1222-
indent -= 2;
1223-
stream << get_indent() << "}\n";
1248+
1249+
print_braced_stmt(op->body);
12241250
}
12251251

12261252
void IRPrinter::visit(const HoistedStorage *op) {
12271253
if (op->name.empty()) {
1228-
stream << get_indent() << "hoisted_storage {\n";
1254+
stream << get_indent() << "hoisted_storage ";
12291255
} else {
1230-
stream << get_indent() << "hoisted_storage (";
1231-
stream << op->name;
1232-
stream << ") {\n";
1256+
stream << get_indent() << "hoisted_storage (" << op->name << ") ";
12331257
}
1234-
indent += 2;
1235-
print(op->body);
1236-
indent -= 2;
1237-
stream << get_indent() << "}\n";
1258+
1259+
print_braced_stmt(op->body);
1260+
}
1261+
1262+
std::string lldb_string(const Expr &ir) {
1263+
std::stringstream s{};
1264+
IRPrinter p(s);
1265+
p.print_no_parens(ir);
1266+
return s.str();
1267+
}
1268+
1269+
std::string lldb_string(const Internal::BaseExprNode *n) {
1270+
return lldb_string(Expr(n));
1271+
}
1272+
1273+
std::string lldb_string(const Stmt &ir) {
1274+
std::stringstream s{};
1275+
IRPrinter p(s);
1276+
p.print_summary(ir);
1277+
return s.str();
12381278
}
12391279

12401280
} // namespace Internal

0 commit comments

Comments
 (0)