Skip to content

Add filtering capabilities to HL_DEBUG_CODEGEN #8627

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

Merged
merged 15 commits into from
May 22, 2025
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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ TEST_CXX_FLAGS += -DLLVM_VERSION=$(LLVM_VERSION_TIMES_10)
# In the tests, default to exporting no symbols that aren't explicitly exported
TEST_CXX_FLAGS += -fvisibility=hidden -fvisibility-inlines-hidden

# In the tests, enable the debug() and internal_assert() macros
TEST_CXX_FLAGS += -DHALIDE_KEEP_MACROS

# gcc 4.8 fires a bogus warning on old versions of png.h
ifneq (,$(findstring g++,$(CXX_VERSION)))
ifneq (,$(findstring 4.8,$(CXX_VERSION)))
Expand Down
3 changes: 3 additions & 0 deletions cmake/HalideTestHelpers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ if (NOT TARGET Halide::Test)
# Obviously link to libHalide, but also grant all tests access to the threads library.
target_link_libraries(Halide_test INTERFACE Halide::Halide Threads::Threads)

# Make internal_assert, debug, etc. available to tests
target_compile_definitions(Halide_test INTERFACE HALIDE_KEEP_MACROS)

# Everyone gets to see the common headers
target_include_directories(Halide_test
INTERFACE
Expand Down
16 changes: 9 additions & 7 deletions src/AddImageChecks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,17 @@ Stmt add_image_checks_inner(Stmt s,
}

// Check that the region passed in (after applying constraints) is within the region used
if (debug::debug_level() >= 3) {
debug(3) << "In image " << name << " region touched is:\n";
debug(3) << [&] {
std::stringstream ss;
ss << "In image " << name << " region touched is:\n";
for (int j = 0; j < dimensions; j++) {
debug(3) << " " << j << ": " << (touched.empty() ? Expr() : touched[j].min)
<< " .. "
<< (touched.empty() ? Expr() : touched[j].max)
<< "\n";
ss << " " << j << ": " << (touched.empty() ? Expr() : touched[j].min)
<< " .. "
<< (touched.empty() ? Expr() : touched[j].max)
<< "\n";
}
}
return ss.str();
}();

for (int j = 0; j < dimensions; j++) {
string dim = std::to_string(j);
Expand Down
48 changes: 22 additions & 26 deletions src/CodeGen_LLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1094,9 +1094,10 @@ void CodeGen_LLVM::optimize_module() {

auto time_start = std::chrono::high_resolution_clock::now();

if (debug::debug_level() >= 3) {
debug(3) << [&] {
module->print(dbgs(), nullptr, false, true);
}
return "";
}();

std::unique_ptr<TargetMachine> tm = make_target_machine(*module);

Expand Down Expand Up @@ -1239,9 +1240,10 @@ void CodeGen_LLVM::optimize_module() {
}

debug(3) << "After LLVM optimizations:\n";
if (debug::debug_level() >= 2) {
debug(2) << [&] {
module->print(dbgs(), nullptr, false, true);
}
return "";
}();

auto *logger = get_compiler_logger();
if (logger) {
Expand All @@ -1264,23 +1266,15 @@ void CodeGen_LLVM::sym_pop(const string &name) {

llvm::Value *CodeGen_LLVM::sym_get(const string &name, bool must_succeed) const {
// look in the symbol table
llvm::Value *const *v = symbol_table.find(name);
if (!v) {
if (must_succeed) {
std::ostringstream err;
err << "Symbol not found: " << name << "\n";

if (debug::debug_level() > 0) {
err << "The following names are in scope:\n"
<< symbol_table << "\n";
}

internal_error << err.str();
} else {
return nullptr;
}
if (const auto *v = symbol_table.find(name)) {
return *v;
}
return *v;
if (must_succeed) {
debug(1) << "The following names are in scope:\n"
<< symbol_table;
internal_error << "Symbol not found: " << name;
}
return nullptr;
}

bool CodeGen_LLVM::sym_exists(const string &name) const {
Expand Down Expand Up @@ -1318,12 +1312,14 @@ Value *CodeGen_LLVM::codegen(const Expr &e) {
e.type().is_handle() ||
value->getType()->isVoidTy() ||
value->getType() == llvm_type_of(e.type());
if (!types_match && debug::debug_level() > 0) {
debug(1) << "Unexpected LLVM type for generated expression. Expected (llvm_type_of(e.type())): ";
llvm_type_of(e.type())->print(dbgs(), true);
debug(1) << " got (value->getType()): ";
value->print(dbgs(), true);
debug(1) << "\n";
if (!types_match) {
debug(1) << [&] {
std::cerr << "Unexpected LLVM type for generated expression. Expected (llvm_type_of(e.type())): ";
llvm_type_of(e.type())->print(dbgs(), true);
std::cerr << " got (value->getType()): ";
value->print(dbgs(), true);
return "\n";
}();
}
internal_assert(types_match)
<< "Codegen of Expr " << e
Expand Down
14 changes: 7 additions & 7 deletions src/CodeGen_PTX_Dev.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,20 +730,19 @@ vector<char> CodeGen_PTX_Dev::compile_to_src() {
module_pass_manager.run(*module);

// Codegen pipeline completed.
if (debug::debug_level() >= 2) {
debug(2) << [&] {
dump();
}
debug(2) << "Done with CodeGen_PTX_Dev::compile_to_src";
return "Done with CodeGen_PTX_Dev::compile_to_src";
}();

debug(1) << "PTX kernel:\n"
<< outstr.c_str() << "\n";

vector<char> buffer(outstr.begin(), outstr.end());

// Dump the SASS too if the cuda SDK is in the path
if (debug::debug_level() >= 2) {
debug(2) << "Compiling PTX to SASS. Will fail if CUDA SDK is not installed (and in the path).\n";

debug(2) << "Compiling PTX to SASS. Will fail if CUDA SDK is not installed (and in the path).\n";
debug(2) << [&] {
TemporaryFile ptx(get_current_kernel_name(), ".ptx");
TemporaryFile sass(get_current_kernel_name(), ".sass");

Expand Down Expand Up @@ -772,7 +771,8 @@ vector<char> CodeGen_PTX_Dev::compile_to_src() {
f.read(buffer.data(), sz);
}
*/
}
return "";
}();

// Null-terminate the ptx source
buffer.push_back(0);
Expand Down
7 changes: 4 additions & 3 deletions src/CodeGen_Vulkan_Dev.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2866,9 +2866,10 @@ void CodeGen_Vulkan_Dev::add_kernel(Stmt stmt,
emitter.encode_spirv_module(spirv_module);

// Dump the SPIR-V if debug is enabled
if (debug::debug_level() >= 2) {
debug(2) << [&] {
emitter.dump_spirv_module();
}
return "";
}();

// Copy the SPIR-V module into the Kernel Module table
KernelModule kernel_module;
Expand Down Expand Up @@ -3059,4 +3060,4 @@ std::unique_ptr<CodeGen_GPU_Dev> new_CodeGen_Vulkan_Dev(const Target &target) {
} // namespace Internal
} // namespace Halide

#endif // WITH_SPIRV
#endif // WITH_SPIRV
121 changes: 111 additions & 10 deletions src/Debug.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,117 @@
#include "Debug.h"
#include "Error.h"
#include "Util.h"

namespace Halide {
namespace Internal {
#include <algorithm>
#include <climits>
#include <optional>

int debug::debug_level() {
static int cached_debug_level = ([]() -> int {
std::string lvl = get_env_variable("HL_DEBUG_CODEGEN");
return !lvl.empty() ? atoi(lvl.c_str()) : 0;
})();
return cached_debug_level;
namespace Halide::Internal {

namespace {

std::string read_until(const char *&str, const char *delims) {
const char *start = str;
for (; *str; ++str) {
for (const char *ch = delims; *ch; ++ch) {
if (*str == *ch) {
return {start, str};
}
}
}
return {start, str};
}

bool parse_int(const std::string &number, int &value) {
const char *start = number.c_str();
char *end;
value = static_cast<int>(strtol(start, &end, 10));
return start < end && *end == '\0';
}

class DebugRule {
int verbosity = 0;
std::string file_suffix = "";
int line_low = -1;
int line_high = INT_MAX;
std::string function_suffix = "";
enum Complexity { VerbosityOnly,
NeedsMatching } complexity = VerbosityOnly;

public:
static std::optional<DebugRule> parse(const std::string &spec) {
DebugRule rule;
const char *ptr = spec.c_str();

if (!parse_int(read_until(ptr, ",@"), rule.verbosity)) {
return std::nullopt;
}

if (*ptr == '\0') {
return rule;
}

if (*ptr == ',') {
rule.file_suffix = read_until(++ptr, ":@");
if (*ptr == ':') {
if (!parse_int(read_until(++ptr, "-@"), rule.line_low)) {
return std::nullopt;
}
rule.line_high = rule.line_low;
if (*ptr == '-') {
if (!parse_int(read_until(++ptr, "@"), rule.line_high)) {
return std::nullopt;
}
}
}
}

if (*ptr == '@') {
rule.function_suffix = std::string{ptr + 1};
}

rule.complexity = NeedsMatching;
return rule;
}

bool accepts(const int verbosity, const char *file, const char *function,
const int line) const {
switch (complexity) {
case VerbosityOnly:
return verbosity <= this->verbosity;
case NeedsMatching:
return verbosity <= this->verbosity &&
ends_with(file, file_suffix) &&
ends_with(function, function_suffix) &&
line_low <= line && line <= line_high;
}
return false;
}
};

std::vector<DebugRule> parse_rules(const std::string &env) {
std::vector<DebugRule> rules;
for (const std::string &spec : split_string(env, ";")) {
if (auto rule = DebugRule::parse(spec)) {
rules.push_back(*rule);
} else if (!spec.empty()) {
user_warning
<< "Ignoring malformed HL_DEBUG_CODEGEN entry: [" << spec << "]\n"
<< "The expected format is:\n "
<< "verbosity[,filename[:line_low[-line_high]]][@func]";
}
}
return rules;
}

} // namespace

bool debug_is_active_impl(const int verbosity, const char *file, const char *function,
const int line) {
static const std::vector<DebugRule> rules = parse_rules(get_env_variable("HL_DEBUG_CODEGEN"));
return std::any_of(rules.begin(), rules.end(), [&](const auto &rule) {
return rule.accepts(verbosity, file, function, line);
});
}

} // namespace Internal
} // namespace Halide
} // namespace Halide::Internal
33 changes: 10 additions & 23 deletions src/Debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,25 @@ std::ostream &operator<<(std::ostream &stream, const Stmt &);
struct LoweredFunc;
std::ostream &operator<<(std::ostream &, const LoweredFunc &);

/** For optional debugging during codegen, use the debug class as
bool debug_is_active_impl(int verbosity, const char *file, const char *function, int line);
#define debug_is_active(n) (::Halide::Internal::debug_is_active_impl((n), __FILE__, __FUNCTION__, __LINE__))

/** For optional debugging during codegen, use the debug macro as
* follows:
*
\code
debug(verbosity) << "The expression is " << expr << "\n";
\endcode
* \code
* debug(verbosity) << "The expression is " << expr << "\n";
* \endcode
*
* verbosity of 0 always prints, 1 should print after every major
* stage, 2 should be used for more detail, and 3 should be used for
* tracing everything that occurs. The verbosity with which to print
* is determined by the value of the environment variable
* HL_DEBUG_CODEGEN
*/

class debug {
const bool logging;

public:
debug(int verbosity)
: logging(verbosity <= debug_level()) {
}

template<typename T>
debug &operator<<(T &&x) {
if (logging) {
std::cerr << std::forward<T>(x);
}
return *this;
}

static int debug_level();
};
#define debug(n) \
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
(!debug_is_active((n))) ? (void)0 : ::Halide::Internal::Voidifier() & std::cerr

/** Allow easily printing the contents of containers, or std::vector-like containers,
* in debug output. Used like so:
Expand Down
Loading
Loading