Skip to content

Commit 7ac0f7e

Browse files
authored
Use a new macro trick to avoid throwing in destructors. (#8774)
1 parent c7acd05 commit 7ac0f7e

File tree

16 files changed

+70
-120
lines changed

16 files changed

+70
-120
lines changed

src/Debug.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ bool debug_is_active_impl(int verbosity, const char *file, const char *function,
4848
* is determined by the value of the environment variable
4949
* HL_DEBUG_CODEGEN
5050
*/
51+
// clang-format off
5152
#define debug(n) \
5253
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
53-
(!debug_is_active((n))) ? (void)0 : ::Halide::Internal::Voidifier() & std::cerr
54+
if (debug_is_active((n))) std::cerr
55+
// clang-format on
5456

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

src/Error.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,6 @@ template<typename T>
150150
}
151151

152152
#ifdef HALIDE_WITH_EXCEPTIONS
153-
if (std::uncaught_exceptions() > 0) {
154-
// This should never happen - evaluating one of the arguments to the
155-
// error message would have to throw an exception. Nonetheless, in
156-
// case it does, preserve the exception already in flight and suppress
157-
// this one.
158-
std::rethrow_exception(std::current_exception());
159-
}
160153
throw e;
161154
#else
162155
std::cerr << e.what() << std::flush;

src/Error.h

Lines changed: 51 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -123,113 +123,95 @@ void issue_warning(const char *warning);
123123

124124
template<typename T>
125125
struct ReportBase {
126-
std::ostringstream msg;
127-
128-
ReportBase(const char *file, const char *function, int line, const char *condition_string, const char *prefix) {
129-
if (debug_is_active_impl(1, file, function, line)) {
130-
msg << prefix << " at " << file << ":" << line << ' ';
131-
if (condition_string) {
132-
msg << "Condition failed: " << condition_string << ' ';
133-
}
134-
}
135-
}
136-
137-
// Just a trick used to convert RValue into LValue
138-
HALIDE_ALWAYS_INLINE T &ref() {
139-
return *static_cast<T *>(this);
140-
}
141-
142126
template<typename S>
143127
HALIDE_ALWAYS_INLINE T &operator<<(const S &x) {
144128
msg << x;
145129
return *static_cast<T *>(this);
146130
}
147131

132+
HALIDE_ALWAYS_INLINE operator bool() const {
133+
return !finalized;
134+
}
135+
148136
protected:
137+
std::ostringstream msg{};
138+
bool finalized{false};
139+
140+
// This function is called as part of issue() below. We can't use a
141+
// virtual function because issue() needs to be marked [[noreturn]]
142+
// for errors and be left alone for warnings (i.e., they have
143+
// different signatures).
149144
std::string finalize_message() {
150145
if (!msg.str().empty() && msg.str().back() != '\n') {
151146
msg << "\n";
152147
}
148+
finalized = true;
153149
return msg.str();
154150
}
151+
152+
T &init(const char *file, const char *function, const int line, const char *condition_string, const char *prefix) {
153+
if (debug_is_active_impl(1, file, function, line)) {
154+
msg << prefix << " at " << file << ":" << line << ' ';
155+
if (condition_string) {
156+
msg << "Condition failed: " << condition_string << ' ';
157+
}
158+
}
159+
return *static_cast<T *>(this);
160+
}
155161
};
156162

157163
template<typename Exception>
158-
struct ErrorReport : ReportBase<ErrorReport<Exception>> {
159-
using Base = ReportBase<ErrorReport>;
160-
161-
ErrorReport(const char *file, const char *function, int line, const char *condition_string)
162-
: Base(file, function, line, condition_string, Exception::error_name) {
163-
this->msg << "Error: ";
164+
struct ErrorReport final : ReportBase<ErrorReport<Exception>> {
165+
ErrorReport &init(const char *file, const char *function, const int line, const char *condition_string) {
166+
return ReportBase<ErrorReport>::init(file, function, line, condition_string, Exception::error_name) << "Error: ";
164167
}
165168

166-
#ifdef _MSC_VER
167-
#pragma warning(push)
168-
#pragma warning(disable : 4722)
169-
#endif
170-
/** When you're done using << on the object, and let it fall out of
171-
* scope, this throws an exception (or aborts if exceptions are disabled).
172-
* This is a little dangerous because the destructor will also be
173-
* called if there's an exception in flight due to an error in one
174-
* of the arguments passed to operator<<. We handle this by rethrowing
175-
* the current exception, if it exists.
176-
*/
177-
[[noreturn]] ~ErrorReport() noexcept(false) {
169+
[[noreturn]] void issue() noexcept(false) {
178170
throw_error(Exception(this->finalize_message()));
179171
}
180-
#ifdef _MSC_VER
181-
#pragma warning(pop)
182-
#endif
183172
};
184173

185-
struct WarningReport : ReportBase<WarningReport> {
186-
WarningReport(const char *file, const char *function, int line, const char *condition_string)
187-
: ReportBase(file, function, line, condition_string, "Warning") {
188-
this->msg << "Warning: ";
174+
struct WarningReport final : ReportBase<WarningReport> {
175+
WarningReport &init(const char *file, const char *function, const int line, const char *condition_string) {
176+
return ReportBase::init(file, function, line, condition_string, "Warning") << "Warning: ";
189177
}
190178

191-
/** When you're done using << on the object, and let it fall out of
192-
* scope, this prints the computed warning message.
193-
*/
194-
~WarningReport() {
179+
void issue() {
195180
issue_warning(this->finalize_message().c_str());
196181
}
197182
};
198183

199-
// This uses operator precedence as a trick to avoid argument evaluation if
200-
// an assertion is true: it is intended to be used as part of the
201-
// _halide_internal_assertion macro, to coerce the result of the stream
202-
// expression to void (to match the condition-is-false case).
203-
struct Voidifier {
204-
// This has to be an operator with a precedence lower than << but
205-
// higher than ?:
206-
template<typename T>
207-
HALIDE_ALWAYS_INLINE void operator&(T &) {
208-
}
209-
};
210-
211184
/**
212-
* _halide_internal_assertion is used to implement our assertion macros
185+
* _halide_internal_diagnostic is used to implement our assertion macros
213186
* in such a way that the messages output for the assertion are only
214187
* evaluated if the assertion's value is false.
215188
*
216-
* Note that this macro intentionally has no parens internally; in actual
217-
* use, the implicit grouping will end up being
218-
*
219-
* condition ? (void) : (Voidifier() & (ErrorReport << arg1 << arg2 ... << argN))
220-
*
221189
* This (regrettably) requires a macro to work, but has the highly desirable
222190
* effect that all assertion parameters are totally skipped (not ever evaluated)
223191
* when the assertion is true.
192+
*
193+
* The macro works by deferring the call to issue() until after the stream
194+
* has been evaluated. This previously used a trick where ErrorReport would
195+
* throw in the destructor, but throwing in a destructor is UB in a lot of
196+
* scenarios, and it was easy to break things by mistake.
224197
*/
225-
#define _halide_internal_assertion(condition, type) \
198+
// clang-format off
199+
#define _halide_internal_diagnostic(condition, type, condition_string) \
226200
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
227-
(condition) ? (void)0 : ::Halide::Internal::Voidifier() & ::Halide::Internal::ErrorReport<type>(__FILE__, __FUNCTION__, __LINE__, #condition).ref()
201+
if (!(condition)) for (type _err; _err; _err.issue()) _err.init(__FILE__, __FUNCTION__, __LINE__, condition_string)
202+
// clang-format on
203+
204+
#define _halide_internal_error(type) \
205+
_halide_internal_diagnostic(0, Halide::Internal::ErrorReport<type>, nullptr)
206+
207+
#define _halide_internal_assertion(condition, type) \
208+
_halide_internal_diagnostic(condition, Halide::Internal::ErrorReport<type>, #condition)
209+
210+
#define user_error _halide_internal_error(Halide::CompileError)
211+
#define internal_error _halide_internal_error(Halide::InternalError)
212+
#define halide_runtime_error _halide_internal_error(Halide::RuntimeError)
228213

229-
#define internal_error Halide::Internal::ErrorReport<Halide::InternalError>(__FILE__, __FUNCTION__, __LINE__, nullptr)
230-
#define user_error Halide::Internal::ErrorReport<Halide::CompileError>(__FILE__, __FUNCTION__, __LINE__, nullptr)
231-
#define user_warning Halide::Internal::WarningReport(__FILE__, __FUNCTION__, __LINE__, nullptr)
232-
#define halide_runtime_error Halide::Internal::ErrorReport<Halide::RuntimeError>(__FILE__, __FUNCTION__, __LINE__, nullptr)
214+
#define user_warning _halide_internal_diagnostic(0, Halide::Internal::WarningReport, nullptr)
233215

234216
#define internal_assert(c) _halide_internal_assertion(c, Halide::InternalError)
235217
#define user_assert(c) _halide_internal_assertion(c, Halide::CompileError)

src/autoschedulers/adams2019/AutoSchedule.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
#include "Cache.h"
5959
#include "CostModel.h"
6060
#include "DefaultCostModel.h"
61-
#include "Errors.h"
6261
#include "Featurization.h"
6362
#include "FunctionDAG.h"
6463
#include "Halide.h"

src/autoschedulers/adams2019/FunctionDAG.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313

1414
#include <vector>
1515

16-
#include "Errors.h"
1716
#include "Featurization.h"
18-
#include "Halide.h"
17+
#include "HalidePlugin.h"
1918

2019
namespace Halide {
2120
namespace Internal {

src/autoschedulers/anderson2021/AutoSchedule.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
#include "AutoSchedule.h"
4949
#include "CostModel.h"
5050
#include "DefaultCostModel.h"
51-
#include "Errors.h"
5251
#include "Featurization.h"
5352
#include "FunctionDAG.h"
5453
#include "LoopNest.h"

src/autoschedulers/anderson2021/FunctionDAG.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
#include <utility>
1313
#include <vector>
1414

15-
#include "Errors.h"
1615
#include "Featurization.h"
17-
#include "Halide.h"
16+
#include "HalidePlugin.h"
1817
#include "PerfectHashMap.h"
1918

2019
namespace Halide {

src/autoschedulers/anderson2021/GPULoopInfo.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#include "GPULoopInfo.h"
2-
#include "Errors.h"
32
#include "LoopNest.h"
43

54
namespace Halide {

src/autoschedulers/anderson2021/GPULoopInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include <memory>
1111
#include <vector>
1212

13-
#include "Halide.h"
13+
#include "HalidePlugin.h"
1414
#include "ThreadInfo.h"
1515

1616
namespace Halide {

src/autoschedulers/anderson2021/GPUMemInfo.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include <vector>
77

88
#include "ASLog.h"
9-
#include "Errors.h"
109

1110
/** \file
1211
*

0 commit comments

Comments
 (0)