From bc39d50558455153da1a44864b4acbdcbc77543b Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Mon, 24 Aug 2026 15:10:21 -0700 Subject: [PATCH 1/3] Only colorize the profiler report when writing to a terminal The report gated ANSI color escapes on the TERM environment variable alone. CI and other redirected environments commonly set TERM=xterm-256color while stdout is a pipe or file, so the escapes were written straight into the captured log as noise. Also require isatty(STDOUT_FILENO) (the report is printed via halide_print, whose default writes to stdout), and honor the NO_COLOR convention. The no-color path already emits plain box-drawing separators, so the table stays well-formed. Co-Authored-By: Claude Opus 4.8 --- src/runtime/profiler_common.cpp | 14 ++++++++++---- src/runtime/runtime_internal.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/runtime/profiler_common.cpp b/src/runtime/profiler_common.cpp index 2384c38f4aca..0d0d27e45593 100644 --- a/src/runtime/profiler_common.cpp +++ b/src/runtime/profiler_common.cpp @@ -513,11 +513,17 @@ ALWAYS_INLINE bool counter_is_approximate(const halide_profiler_func_stats *fs, WEAK void halide_profiler_report_unlocked(void *user_context, halide_profiler_state *s) { StringStreamPrinter<1024> sstr(user_context); - bool support_colors = false; + // Emit ANSI color escapes only when the report is going to an actual + // color-capable terminal. Checking TERM alone isn't enough: CI and other + // redirected environments often set TERM=xterm-256color while stdout is a + // pipe or file, which would splatter escape codes into the captured log. + // The report is printed via halide_print, whose default writes to stdout. + const char *no_color = getenv("NO_COLOR"); const char *term = getenv("TERM"); - if (term && (strstr(term, "color") || strstr(term, "xterm"))) { - support_colors = true; - } + bool support_colors = + !(no_color && no_color[0]) && + term && (strstr(term, "color") || strstr(term, "xterm")) && + isatty(STDOUT_FILENO); // Column-aligned rows are produced from `const char *` templates. A // run of an uppercase marker char is a slot — the marker picks the diff --git a/src/runtime/runtime_internal.h b/src/runtime/runtime_internal.h index fdf951c7dc87..11c63c94b274 100644 --- a/src/runtime/runtime_internal.h +++ b/src/runtime/runtime_internal.h @@ -121,6 +121,7 @@ int fclose(void *); int close(int); size_t fwrite(const void *, size_t, size_t, void *); ssize_t write(int fd, const void *buf, size_t bytes); +int isatty(int fd); int remove(const char *pathname); int ioctl(int fd, unsigned long request, ...); char *strncpy(char *dst, const char *src, size_t n); From a93de9eaecc6645472081e0a20e00ca5c045fdee Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Mon, 31 Aug 2026 12:34:51 -0700 Subject: [PATCH 2/3] Profiler: honor HL_COLORS in the report color gate Match IRPrinter's HL_COLORS handling: if HL_COLORS is set it is an explicit override ("0" off, anything else on); otherwise fall back to honoring NO_COLOR and auto-detecting a color-capable terminal. Also add isatty to the list of posix calls that get an underscore prefix on Windows (_isatty). Co-Authored-By: Claude Opus 4.8 --- src/LLVM_Runtime_Linker.cpp | 2 +- src/runtime/profiler_common.cpp | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/LLVM_Runtime_Linker.cpp b/src/LLVM_Runtime_Linker.cpp index dc612a15ddd7..2f90ec424f8c 100644 --- a/src/LLVM_Runtime_Linker.cpp +++ b/src/LLVM_Runtime_Linker.cpp @@ -826,7 +826,7 @@ void add_underscore_to_posix_call(llvm::CallInst *call, llvm::Function *fn, llvm * of mcjit, so we just rewrite uses of these functions to include an * underscore. */ void add_underscores_to_posix_calls_on_windows(llvm::Module *m) { - string posix_fns[] = {"vsnprintf", "open", "close", "write", "fileno"}; + string posix_fns[] = {"vsnprintf", "open", "close", "write", "fileno", "isatty"}; string *posix_fns_begin = posix_fns; string *posix_fns_end = posix_fns + sizeof(posix_fns) / sizeof(posix_fns[0]); diff --git a/src/runtime/profiler_common.cpp b/src/runtime/profiler_common.cpp index 0d0d27e45593..40e105f812e3 100644 --- a/src/runtime/profiler_common.cpp +++ b/src/runtime/profiler_common.cpp @@ -513,17 +513,25 @@ ALWAYS_INLINE bool counter_is_approximate(const halide_profiler_func_stats *fs, WEAK void halide_profiler_report_unlocked(void *user_context, halide_profiler_state *s) { StringStreamPrinter<1024> sstr(user_context); - // Emit ANSI color escapes only when the report is going to an actual - // color-capable terminal. Checking TERM alone isn't enough: CI and other - // redirected environments often set TERM=xterm-256color while stdout is a - // pipe or file, which would splatter escape codes into the captured log. - // The report is printed via halide_print, whose default writes to stdout. - const char *no_color = getenv("NO_COLOR"); - const char *term = getenv("TERM"); - bool support_colors = - !(no_color && no_color[0]) && - term && (strstr(term, "color") || strstr(term, "xterm")) && - isatty(STDOUT_FILENO); + // Decide whether to emit ANSI color escapes. HL_COLORS, if set, is an + // explicit override matching IRPrinter: "0" forces colors off, anything + // else on. Otherwise honor NO_COLOR and auto-detect a color-capable + // terminal. Checking TERM alone isn't enough: CI and other redirected + // environments often set TERM=xterm-256color while stdout is a pipe or + // file, which would splatter escape codes into the captured log. The + // report is printed via halide_print, whose default writes to stdout. + bool support_colors; + const char *hl_colors = getenv("HL_COLORS"); + if (hl_colors) { + support_colors = atoi(hl_colors) != 0; + } else { + const char *no_color = getenv("NO_COLOR"); + const char *term = getenv("TERM"); + support_colors = + !(no_color && no_color[0]) && + term && (strstr(term, "color") || strstr(term, "xterm")) && + isatty(STDOUT_FILENO); + } // Column-aligned rows are produced from `const char *` templates. A // run of an uppercase marker char is a slot — the marker picks the From bc836974ba4dc8a7c0f62e7c91716eaa3a12c561 Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Tue, 1 Sep 2026 11:42:15 -0700 Subject: [PATCH 3/3] IRPrinter: respect NO_COLOR in the color gate Match the profiler report's logic exactly: HL_COLORS is an explicit override (atoi() != 0); otherwise honor NO_COLOR in addition to detecting a color-capable terminal. Previously NO_COLOR was ignored here. Co-Authored-By: Claude Opus 4.8 --- src/IRPrinter.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/IRPrinter.cpp b/src/IRPrinter.cpp index 8ee4eceae0f6..31c192a5d443 100644 --- a/src/IRPrinter.cpp +++ b/src/IRPrinter.cpp @@ -557,7 +557,14 @@ IRPrinter::IRPrinter(ostream &s) auto detect_color = [&](const std::ostream *terminal) { std::string opt = get_env_variable("HL_COLORS"); - bool use_colors = !opt.empty() ? opt == "1" : supports_ansi(terminal); + bool use_colors; + if (!opt.empty()) { + use_colors = opt == "1"; + } else { + // Respect NO_COLOR in addition to whether we're writing to a + // terminal, matching the profiler report's color gate. + use_colors = get_env_variable("NO_COLOR").empty() && supports_ansi(terminal); + } if (use_colors) { ansi = true;