From cd2be5e9f46d3225e1433023eb6565c10e3eb3ac Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 20 Aug 2025 15:39:56 -0400 Subject: [PATCH 1/7] Use a new macro trick to avoid throwing in destructors. --- src/Error.cpp | 7 -- src/Error.h | 68 ++++++++----------- src/autoschedulers/adams2019/AutoSchedule.cpp | 1 - src/autoschedulers/adams2019/FunctionDAG.h | 3 +- .../anderson2021/AutoSchedule.cpp | 1 - src/autoschedulers/anderson2021/FunctionDAG.h | 3 +- .../anderson2021/GPULoopInfo.cpp | 1 - src/autoschedulers/anderson2021/GPULoopInfo.h | 2 +- src/autoschedulers/anderson2021/GPUMemInfo.h | 1 - src/autoschedulers/anderson2021/ThreadInfo.h | 2 +- src/autoschedulers/common/Errors.h | 13 ---- src/autoschedulers/common/HalidePlugin.h | 2 - src/autoschedulers/common/ParamParser.h | 2 +- .../li2018/GradientAutoscheduler.cpp | 1 - 14 files changed, 34 insertions(+), 73 deletions(-) delete mode 100644 src/autoschedulers/common/Errors.h diff --git a/src/Error.cpp b/src/Error.cpp index d652f6e76496..16afa8bc1fd6 100644 --- a/src/Error.cpp +++ b/src/Error.cpp @@ -150,13 +150,6 @@ template } #ifdef HALIDE_WITH_EXCEPTIONS - if (std::uncaught_exceptions() > 0) { - // This should never happen - evaluating one of the arguments to the - // error message would have to throw an exception. Nonetheless, in - // case it does, preserve the exception already in flight and suppress - // this one. - std::rethrow_exception(std::current_exception()); - } throw e; #else std::cerr << e.what() << std::flush; diff --git a/src/Error.h b/src/Error.h index e7e854d40259..53e33c20a3fc 100644 --- a/src/Error.h +++ b/src/Error.h @@ -123,8 +123,6 @@ void issue_warning(const char *warning); template struct ReportBase { - std::ostringstream msg; - ReportBase(const char *file, const char *function, int line, const char *condition_string, const char *prefix) { if (debug_is_active_impl(1, file, function, line)) { msg << prefix << " at " << file << ":" << line << ' '; @@ -134,22 +132,24 @@ struct ReportBase { } } - // Just a trick used to convert RValue into LValue - HALIDE_ALWAYS_INLINE T &ref() { - return *static_cast(this); - } - template HALIDE_ALWAYS_INLINE T &operator<<(const S &x) { msg << x; return *static_cast(this); } + HALIDE_ALWAYS_INLINE operator bool() const { + return !issued; + } + protected: + std::ostringstream msg{}; + bool issued{false}; std::string finalize_message() { if (!msg.str().empty() && msg.str().back() != '\n') { msg << "\n"; } + issued = true; return msg.str(); } }; @@ -162,24 +162,9 @@ struct ErrorReport : ReportBase> { : Base(file, function, line, condition_string, Exception::error_name) { this->msg << "Error: "; } - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4722) -#endif - /** When you're done using << on the object, and let it fall out of - * scope, this throws an exception (or aborts if exceptions are disabled). - * This is a little dangerous because the destructor will also be - * called if there's an exception in flight due to an error in one - * of the arguments passed to operator<<. We handle this by rethrowing - * the current exception, if it exists. - */ - [[noreturn]] ~ErrorReport() noexcept(false) { + [[noreturn]] void issue() noexcept(false) { throw_error(Exception(this->finalize_message())); } -#ifdef _MSC_VER -#pragma warning(pop) -#endif }; struct WarningReport : ReportBase { @@ -187,11 +172,7 @@ struct WarningReport : ReportBase { : ReportBase(file, function, line, condition_string, "Warning") { this->msg << "Warning: "; } - - /** When you're done using << on the object, and let it fall out of - * scope, this prints the computed warning message. - */ - ~WarningReport() { + void issue() { issue_warning(this->finalize_message().c_str()); } }; @@ -213,23 +194,32 @@ struct Voidifier { * in such a way that the messages output for the assertion are only * evaluated if the assertion's value is false. * - * Note that this macro intentionally has no parens internally; in actual - * use, the implicit grouping will end up being - * - * condition ? (void) : (Voidifier() & (ErrorReport << arg1 << arg2 ... << argN)) - * * This (regrettably) requires a macro to work, but has the highly desirable * effect that all assertion parameters are totally skipped (not ever evaluated) * when the assertion is true. + * + * The macro works by deferring the call to issue() until after the stream + * has been evaluated. This used to use a trick where ErrorReport would throw + * in the destructor, but throwing destructors are UB in a lot of scenarios, + * and it was easy to break things by mistake. */ -#define _halide_internal_assertion(condition, type) \ +// clang-format off +#define _halide_internal_diagnostic(condition, type, condition_string) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - (condition) ? (void)0 : ::Halide::Internal::Voidifier() & ::Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, #condition).ref() + if (!(condition)) for (type _err(__FILE__, __FUNCTION__, __LINE__, condition_string); _err; _err.issue()) _err +// clang-format on + +#define _halide_internal_error(type) \ + _halide_internal_diagnostic(0, Halide::Internal::ErrorReport, nullptr) + +#define _halide_internal_assertion(condition, type) \ + _halide_internal_diagnostic(condition, Halide::Internal::ErrorReport, #condition) + +#define user_error _halide_internal_error(Halide::CompileError) +#define internal_error _halide_internal_error(Halide::InternalError) +#define halide_runtime_error _halide_internal_error(Halide::RuntimeError) -#define internal_error Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, nullptr) -#define user_error Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, nullptr) -#define user_warning Halide::Internal::WarningReport(__FILE__, __FUNCTION__, __LINE__, nullptr) -#define halide_runtime_error Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, nullptr) +#define user_warning _halide_internal_diagnostic(0, Halide::Internal::WarningReport, nullptr) #define internal_assert(c) _halide_internal_assertion(c, Halide::InternalError) #define user_assert(c) _halide_internal_assertion(c, Halide::CompileError) diff --git a/src/autoschedulers/adams2019/AutoSchedule.cpp b/src/autoschedulers/adams2019/AutoSchedule.cpp index ae3ecbe8fb0b..1dde5de1d6e4 100644 --- a/src/autoschedulers/adams2019/AutoSchedule.cpp +++ b/src/autoschedulers/adams2019/AutoSchedule.cpp @@ -58,7 +58,6 @@ #include "Cache.h" #include "CostModel.h" #include "DefaultCostModel.h" -#include "Errors.h" #include "Featurization.h" #include "FunctionDAG.h" #include "Halide.h" diff --git a/src/autoschedulers/adams2019/FunctionDAG.h b/src/autoschedulers/adams2019/FunctionDAG.h index 4624f54b2f25..7dc3d4bed580 100644 --- a/src/autoschedulers/adams2019/FunctionDAG.h +++ b/src/autoschedulers/adams2019/FunctionDAG.h @@ -13,9 +13,8 @@ #include -#include "Errors.h" #include "Featurization.h" -#include "Halide.h" +#include "HalidePlugin.h" namespace Halide { namespace Internal { diff --git a/src/autoschedulers/anderson2021/AutoSchedule.cpp b/src/autoschedulers/anderson2021/AutoSchedule.cpp index 35207bf640cf..c1abbae18dbf 100644 --- a/src/autoschedulers/anderson2021/AutoSchedule.cpp +++ b/src/autoschedulers/anderson2021/AutoSchedule.cpp @@ -48,7 +48,6 @@ #include "AutoSchedule.h" #include "CostModel.h" #include "DefaultCostModel.h" -#include "Errors.h" #include "Featurization.h" #include "FunctionDAG.h" #include "LoopNest.h" diff --git a/src/autoschedulers/anderson2021/FunctionDAG.h b/src/autoschedulers/anderson2021/FunctionDAG.h index 4e08a917f8ca..eea0f6ddd9f4 100644 --- a/src/autoschedulers/anderson2021/FunctionDAG.h +++ b/src/autoschedulers/anderson2021/FunctionDAG.h @@ -12,9 +12,8 @@ #include #include -#include "Errors.h" #include "Featurization.h" -#include "Halide.h" +#include "HalidePlugin.h" #include "PerfectHashMap.h" namespace Halide { diff --git a/src/autoschedulers/anderson2021/GPULoopInfo.cpp b/src/autoschedulers/anderson2021/GPULoopInfo.cpp index 0f611be599ee..e85f077d088b 100644 --- a/src/autoschedulers/anderson2021/GPULoopInfo.cpp +++ b/src/autoschedulers/anderson2021/GPULoopInfo.cpp @@ -1,5 +1,4 @@ #include "GPULoopInfo.h" -#include "Errors.h" #include "LoopNest.h" namespace Halide { diff --git a/src/autoschedulers/anderson2021/GPULoopInfo.h b/src/autoschedulers/anderson2021/GPULoopInfo.h index 7fe5a62dcf11..e7700458fa49 100644 --- a/src/autoschedulers/anderson2021/GPULoopInfo.h +++ b/src/autoschedulers/anderson2021/GPULoopInfo.h @@ -10,7 +10,7 @@ #include #include -#include "Halide.h" +#include "HalidePlugin.h" #include "ThreadInfo.h" namespace Halide { diff --git a/src/autoschedulers/anderson2021/GPUMemInfo.h b/src/autoschedulers/anderson2021/GPUMemInfo.h index 40c000e1b8c1..fde428c4bb1e 100644 --- a/src/autoschedulers/anderson2021/GPUMemInfo.h +++ b/src/autoschedulers/anderson2021/GPUMemInfo.h @@ -6,7 +6,6 @@ #include #include "ASLog.h" -#include "Errors.h" /** \file * diff --git a/src/autoschedulers/anderson2021/ThreadInfo.h b/src/autoschedulers/anderson2021/ThreadInfo.h index 3482c2e1002d..0ec198667947 100644 --- a/src/autoschedulers/anderson2021/ThreadInfo.h +++ b/src/autoschedulers/anderson2021/ThreadInfo.h @@ -10,8 +10,8 @@ #include -#include "Errors.h" #include "FunctionDAG.h" +#include "HalidePlugin.h" namespace Halide { namespace Internal { diff --git a/src/autoschedulers/common/Errors.h b/src/autoschedulers/common/Errors.h deleted file mode 100644 index d10c4537e1ff..000000000000 --- a/src/autoschedulers/common/Errors.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef ERRORS_H -#define ERRORS_H - -#include "Halide.h" - -#define internal_error Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, nullptr) -#define user_error Halide::Internal::ErrorReport(__FILE__, __FUNCTION__, __LINE__, nullptr) -#define user_warning Halide::Internal::WarningReport(__FILE__, __FUNCTION__, __LINE__, nullptr) - -#define user_assert(c) _halide_internal_assertion(c, Halide::CompileError) -#define internal_assert(c) _halide_internal_assertion(c, Halide::InternalError) - -#endif diff --git a/src/autoschedulers/common/HalidePlugin.h b/src/autoschedulers/common/HalidePlugin.h index fc97c33cafa8..69b818382127 100644 --- a/src/autoschedulers/common/HalidePlugin.h +++ b/src/autoschedulers/common/HalidePlugin.h @@ -4,8 +4,6 @@ #define HALIDE_KEEP_MACROS #include "Halide.h" -#include "Errors.h" - #define REGISTER_AUTOSCHEDULER(NAME) \ struct HALIDE_EXPORT Register##NAME { \ Register##NAME() { \ diff --git a/src/autoschedulers/common/ParamParser.h b/src/autoschedulers/common/ParamParser.h index 25c943f6ce11..f4352baee75d 100644 --- a/src/autoschedulers/common/ParamParser.h +++ b/src/autoschedulers/common/ParamParser.h @@ -1,7 +1,7 @@ #ifndef PARSE_H #define PARSE_H -#include "Errors.h" +#include "HalidePlugin.h" #include #include diff --git a/src/autoschedulers/li2018/GradientAutoscheduler.cpp b/src/autoschedulers/li2018/GradientAutoscheduler.cpp index 5ab2d6d96245..d9f0336d1040 100644 --- a/src/autoschedulers/li2018/GradientAutoscheduler.cpp +++ b/src/autoschedulers/li2018/GradientAutoscheduler.cpp @@ -1,6 +1,5 @@ #include "HalidePlugin.h" -#include "Errors.h" #include "ParamParser.h" namespace Halide { From a8cb4194e7b93fa75bb8df4b69c25c94e1efde90 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 20 Aug 2025 15:51:05 -0400 Subject: [PATCH 2/7] Use for-loop trick for debug(). Remove Voidifier. --- src/Debug.h | 4 +++- src/Error.h | 12 ------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Debug.h b/src/Debug.h index 4e2e88bf6fbd..3378fc8f6daa 100644 --- a/src/Debug.h +++ b/src/Debug.h @@ -48,9 +48,11 @@ bool debug_is_active_impl(int verbosity, const char *file, const char *function, * is determined by the value of the environment variable * HL_DEBUG_CODEGEN */ +// clang-format off #define debug(n) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - (!debug_is_active((n))) ? (void)0 : ::Halide::Internal::Voidifier() & std::cerr + for (int _ran = 0; !_ran && debug_is_active((n)); ++_ran) std::cerr +// clang-format on /** Allow easily printing the contents of containers, or std::vector-like containers, * in debug output. Used like so: diff --git a/src/Error.h b/src/Error.h index 53e33c20a3fc..88e126d97e29 100644 --- a/src/Error.h +++ b/src/Error.h @@ -177,18 +177,6 @@ struct WarningReport : ReportBase { } }; -// This uses operator precedence as a trick to avoid argument evaluation if -// an assertion is true: it is intended to be used as part of the -// _halide_internal_assertion macro, to coerce the result of the stream -// expression to void (to match the condition-is-false case). -struct Voidifier { - // This has to be an operator with a precedence lower than << but - // higher than ?: - template - HALIDE_ALWAYS_INLINE void operator&(T &) { - } -}; - /** * _halide_internal_assertion is used to implement our assertion macros * in such a way that the messages output for the assertion are only From 8b378ccbc4bb54c2e6e5575269b1ff279431c3ca Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Thu, 21 Aug 2025 06:35:08 -0400 Subject: [PATCH 3/7] Fix custom_error_reporter.cpp test --- test/correctness/custom_error_reporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/correctness/custom_error_reporter.cpp b/test/correctness/custom_error_reporter.cpp index f7432b1c3bdc..d9e3bbf2612f 100644 --- a/test/correctness/custom_error_reporter.cpp +++ b/test/correctness/custom_error_reporter.cpp @@ -62,10 +62,10 @@ int main(int argc, char **argv) { MyCustomErrorReporter reporter; set_custom_compile_time_error_reporter(&reporter); - Halide::Internal::WarningReport("", "", 0, nullptr) << "Here is a warning."; + user_warning << "Here is a warning."; // This call should not return. - _halide_user_assert(argc == 0) << should_be_evaluated(); + user_assert(argc == 0) << should_be_evaluated(); printf("CompileTimeErrorReporter::error() must not return.\n"); return 1; From dff25f11a738d550a00b23685ca8e4ca22306eba Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Thu, 21 Aug 2025 06:51:26 -0400 Subject: [PATCH 4/7] Convince GCC the macro has an effect. --- src/Error.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Error.h b/src/Error.h index 88e126d97e29..dd1f35b72b36 100644 --- a/src/Error.h +++ b/src/Error.h @@ -123,15 +123,6 @@ void issue_warning(const char *warning); template struct ReportBase { - ReportBase(const char *file, const char *function, int line, const char *condition_string, const char *prefix) { - if (debug_is_active_impl(1, file, function, line)) { - msg << prefix << " at " << file << ":" << line << ' '; - if (condition_string) { - msg << "Condition failed: " << condition_string << ' '; - } - } - } - template HALIDE_ALWAYS_INLINE T &operator<<(const S &x) { msg << x; @@ -145,6 +136,7 @@ struct ReportBase { protected: std::ostringstream msg{}; bool issued{false}; + std::string finalize_message() { if (!msg.str().empty() && msg.str().back() != '\n') { msg << "\n"; @@ -152,26 +144,34 @@ struct ReportBase { issued = true; return msg.str(); } + + T &init(const char *file, const char *function, const int line, const char *condition_string, const char *prefix) { + if (debug_is_active_impl(1, file, function, line)) { + msg << prefix << " at " << file << ":" << line << ' '; + if (condition_string) { + msg << "Condition failed: " << condition_string << ' '; + } + } + return *static_cast(this); + } }; template struct ErrorReport : ReportBase> { - using Base = ReportBase; - - ErrorReport(const char *file, const char *function, int line, const char *condition_string) - : Base(file, function, line, condition_string, Exception::error_name) { - this->msg << "Error: "; + ErrorReport &init(const char *file, const char *function, const int line, const char *condition_string) { + return ReportBase::init(file, function, line, condition_string, Exception::error_name) << "Error: "; } + [[noreturn]] void issue() noexcept(false) { throw_error(Exception(this->finalize_message())); } }; struct WarningReport : ReportBase { - WarningReport(const char *file, const char *function, int line, const char *condition_string) - : ReportBase(file, function, line, condition_string, "Warning") { - this->msg << "Warning: "; + WarningReport &init(const char *file, const char *function, const int line, const char *condition_string) { + return ReportBase::init(file, function, line, condition_string, "Warning") << "Warning: "; } + void issue() { issue_warning(this->finalize_message().c_str()); } @@ -194,7 +194,7 @@ struct WarningReport : ReportBase { // clang-format off #define _halide_internal_diagnostic(condition, type, condition_string) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - if (!(condition)) for (type _err(__FILE__, __FUNCTION__, __LINE__, condition_string); _err; _err.issue()) _err + if (!(condition)) for (type _err; _err; _err.issue()) _err.init(__FILE__, __FUNCTION__, __LINE__, condition_string) // clang-format on #define _halide_internal_error(type) \ From ea437b64e23628d74cbe41af46de53145d234929 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Thu, 21 Aug 2025 06:55:16 -0400 Subject: [PATCH 5/7] Clean up custom_error_reporter.cpp test --- test/correctness/custom_error_reporter.cpp | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/test/correctness/custom_error_reporter.cpp b/test/correctness/custom_error_reporter.cpp index d9e3bbf2612f..f85c33dc320b 100644 --- a/test/correctness/custom_error_reporter.cpp +++ b/test/correctness/custom_error_reporter.cpp @@ -1,6 +1,6 @@ #include "Halide.h" -#include -#include +#include +#include using namespace Halide; @@ -18,27 +18,22 @@ int should_never_be_evaluated() { return 0; } -class MyCustomErrorReporter : public Halide::CompileTimeErrorReporter { -public: - int errors_occurred; - int warnings_occurred; - - MyCustomErrorReporter() - : errors_occurred(0), warnings_occurred(0) { - } +struct MyCustomErrorReporter final : CompileTimeErrorReporter { + int errors_occurred{0}; + int warnings_occurred{0}; void warning(const char *msg) override { - auto msg_safe = Halide::Internal::replace_all(msg, ":", "(semicolon)"); + const auto msg_safe = Internal::replace_all(msg, ":", "(colon)"); printf("Custom warn: %s\n", msg_safe.c_str()); warnings_occurred++; } [[noreturn]] void error(const char *msg) override { // Emitting "error.*:" to stdout or stderr will cause CMake to report the - // test as a failure on Windows, regardless of error code returned. + // test as a failure on Windows, regardless of the error code returned. // The error text we get from ErrorReport probably contains some variant // of this, so let's make sure it doesn't match that pattern. - auto msg_safe = Halide::Internal::replace_all(msg, ":", "(semicolon)"); + const auto msg_safe = Internal::replace_all(msg, ":", "(colon)"); printf("Custom err: %s\n", msg_safe.c_str()); errors_occurred++; @@ -56,7 +51,7 @@ class MyCustomErrorReporter : public Halide::CompileTimeErrorReporter { int main(int argc, char **argv) { // Use argc here so that the compiler cannot optimize it away: - // we know argc > 0 always, but compiler (probably) doesn't. + // we know argc > 0 always, but the compiler (probably) doesn't. _halide_user_assert(argc > 0) << should_never_be_evaluated(); MyCustomErrorReporter reporter; From a9df4abb338ca0e82b8d308044bd076acf188db1 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Thu, 21 Aug 2025 06:58:49 -0400 Subject: [PATCH 6/7] Just use an if() for debug()... --- src/Debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Debug.h b/src/Debug.h index 3378fc8f6daa..c0b6c25afcb4 100644 --- a/src/Debug.h +++ b/src/Debug.h @@ -51,7 +51,7 @@ bool debug_is_active_impl(int verbosity, const char *file, const char *function, // clang-format off #define debug(n) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - for (int _ran = 0; !_ran && debug_is_active((n)); ++_ran) std::cerr + if (debug_is_active((n))) std::cerr // clang-format on /** Allow easily printing the contents of containers, or std::vector-like containers, From 09ebe9bac7f8291b6280c2b263e9a67b2f5231af Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Thu, 21 Aug 2025 14:00:11 -0400 Subject: [PATCH 7/7] Respond to review comments --- src/Error.h | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Error.h b/src/Error.h index dd1f35b72b36..cbf2f662d45d 100644 --- a/src/Error.h +++ b/src/Error.h @@ -130,18 +130,22 @@ struct ReportBase { } HALIDE_ALWAYS_INLINE operator bool() const { - return !issued; + return !finalized; } protected: std::ostringstream msg{}; - bool issued{false}; + bool finalized{false}; + // This function is called as part of issue() below. We can't use a + // virtual function because issue() needs to be marked [[noreturn]] + // for errors and be left alone for warnings (i.e., they have + // different signatures). std::string finalize_message() { if (!msg.str().empty() && msg.str().back() != '\n') { msg << "\n"; } - issued = true; + finalized = true; return msg.str(); } @@ -157,7 +161,7 @@ struct ReportBase { }; template -struct ErrorReport : ReportBase> { +struct ErrorReport final : ReportBase> { ErrorReport &init(const char *file, const char *function, const int line, const char *condition_string) { return ReportBase::init(file, function, line, condition_string, Exception::error_name) << "Error: "; } @@ -167,7 +171,7 @@ struct ErrorReport : ReportBase> { } }; -struct WarningReport : ReportBase { +struct WarningReport final : ReportBase { WarningReport &init(const char *file, const char *function, const int line, const char *condition_string) { return ReportBase::init(file, function, line, condition_string, "Warning") << "Warning: "; } @@ -178,7 +182,7 @@ struct WarningReport : ReportBase { }; /** - * _halide_internal_assertion is used to implement our assertion macros + * _halide_internal_diagnostic is used to implement our assertion macros * in such a way that the messages output for the assertion are only * evaluated if the assertion's value is false. * @@ -187,9 +191,9 @@ struct WarningReport : ReportBase { * when the assertion is true. * * The macro works by deferring the call to issue() until after the stream - * has been evaluated. This used to use a trick where ErrorReport would throw - * in the destructor, but throwing destructors are UB in a lot of scenarios, - * and it was easy to break things by mistake. + * has been evaluated. This previously used a trick where ErrorReport would + * throw in the destructor, but throwing in a destructor is UB in a lot of + * scenarios, and it was easy to break things by mistake. */ // clang-format off #define _halide_internal_diagnostic(condition, type, condition_string) \