https://github.com/halide/Halide
Tip revision: 9b4d81c2b8540c103b8aed23ef5c7e6d218d31a5 authored by Andrew Adams on 06 June 2024, 22:26:31 UTC
Merge remote-tracking branch 'origin/main' into abadams/promise_clamped_is_a_tag
Merge remote-tracking branch 'origin/main' into abadams/promise_clamped_is_a_tag
Tip revision: 9b4d81c
printer.h
#ifndef HALIDE_RUNTIME_PRINTER_H
#define HALIDE_RUNTIME_PRINTER_H
#include "HalideRuntime.h"
// This is useful for debugging threading issues in the Halide runtime:
// prefix all `debug()` statements with the thread id that did the logging.
// Left here (but disabled) for future reference.
#ifndef HALIDE_RUNTIME_PRINTER_LOG_THREADID
#define HALIDE_RUNTIME_PRINTER_LOG_THREADID 0
#endif
#if HALIDE_RUNTIME_PRINTER_LOG_THREADID
extern "C" int pthread_threadid_np(long thread, uint64_t *thread_id);
#endif
namespace Halide {
namespace Runtime {
namespace Internal {
enum PrinterType { BasicPrinterType = 0,
ErrorPrinterType = 1,
StringStreamPrinterType = 2 };
constexpr uint64_t default_printer_buffer_length = 1024;
// A class for constructing debug messages from the runtime. Dumps
// items into a stack array, then prints them when the object leaves
// scope using halide_print. Think of it as a stringstream that prints
// when it dies. Use it like this:
// debug(user_context) << "A" << b << c << "\n";
// If you use it like this:
// debug d(user_context);
// d << "A";
// d << b;
// d << c << "\n";
// Then remember the print only happens when the debug object leaves
// scope, which may print at a confusing time.
class PrinterBase {
protected:
char *dst;
char *const end;
char *const start;
void *const user_context;
NEVER_INLINE void allocation_error() const {
halide_error(user_context, "Printer buffer allocation failed.\n");
}
public:
// This class will stream text into the range [start, start + size - 1].
// It does *not* assume any ownership of the memory; it assumes
// the memory will remain valid for its lifespan, and doesn't
// attempt to free any allocations. It also doesn't do any sanity
// checking of the pointers, so if you pass in a null or bogus value,
// it will attempt to use it.
NEVER_INLINE PrinterBase(void *user_context_, char *start_, uint64_t size_)
: dst(start_),
// (If start is null, set end = start to ensure no writes are done)
end(start_ ? start_ + size_ - 1 : start_),
start(start_),
user_context(user_context_) {
if (end > start) {
// null-terminate the final byte to ensure string isn't $ENDLESS
*end = 0;
}
}
NEVER_INLINE const char *str() {
(void)halide_msan_annotate_memory_is_initialized(user_context, start, dst - start + 1);
return start;
}
uint64_t size() const {
halide_debug_assert(user_context, dst >= start);
return (uint64_t)(dst - start);
}
uint64_t capacity() const {
halide_debug_assert(user_context, end >= start);
return (uint64_t)(end - start);
}
NEVER_INLINE void clear() {
dst = start;
if (dst) {
dst[0] = 0;
}
}
NEVER_INLINE void erase(int n) {
if (dst) {
dst -= n;
if (dst < start) {
dst = start;
}
dst[0] = 0;
}
}
struct Float16Bits {
uint16_t bits;
};
// These are NEVER_INLINE because Clang will aggressively inline
// all of them, but the code size of calling out-of-line here is slightly
// smaller, and we ~always prefer smaller code size when using Printer
// in the runtime (it's a modest but nonzero difference).
NEVER_INLINE PrinterBase &operator<<(const char *arg) {
dst = halide_string_to_string(dst, end, arg);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(int64_t arg) {
dst = halide_int64_to_string(dst, end, arg, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(int32_t arg) {
dst = halide_int64_to_string(dst, end, arg, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(uint64_t arg) {
dst = halide_uint64_to_string(dst, end, arg, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(uint32_t arg) {
dst = halide_uint64_to_string(dst, end, arg, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(double arg) {
dst = halide_double_to_string(dst, end, arg, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(float arg) {
dst = halide_double_to_string(dst, end, arg, 0);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(Float16Bits arg) {
double value = halide_float16_bits_to_double(arg.bits);
dst = halide_double_to_string(dst, end, value, 1);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(const void *arg) {
dst = halide_pointer_to_string(dst, end, arg);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(const halide_type_t &t) {
dst = halide_type_to_string(dst, end, &t);
return *this;
}
NEVER_INLINE PrinterBase &operator<<(const halide_buffer_t &buf) {
dst = halide_buffer_to_string(dst, end, &buf);
return *this;
}
template<typename... Args>
void append(const Args &...args) {
((*this << args), ...);
}
// Not movable, not copyable
PrinterBase(const PrinterBase ©) = delete;
PrinterBase &operator=(const PrinterBase &) = delete;
PrinterBase(PrinterBase &&) = delete;
PrinterBase &operator=(PrinterBase &&) = delete;
};
namespace {
template<PrinterType printer_type, uint64_t buffer_length = default_printer_buffer_length>
class HeapPrinter : public PrinterBase {
public:
NEVER_INLINE explicit HeapPrinter(void *user_context_)
: PrinterBase(user_context_, (char *)malloc(buffer_length), buffer_length) {
if (!start) {
allocation_error();
}
#if HALIDE_RUNTIME_PRINTER_LOG_THREADID
uint64_t tid;
pthread_threadid_np(0, &tid);
*this << "(TID:" << tid << ")";
#endif
}
NEVER_INLINE ~HeapPrinter() {
if (printer_type == ErrorPrinterType) {
halide_error(user_context, str());
} else if (printer_type == BasicPrinterType) {
halide_print(user_context, str());
} else {
// It's a stringstream. Do nothing.
}
free(start);
}
};
// A class that supports << with all the same types as Printer, but
// does nothing and should compile to a no-op.
class SinkPrinter {
public:
ALWAYS_INLINE explicit SinkPrinter(void *user_context) {
}
};
template<typename T>
ALWAYS_INLINE SinkPrinter operator<<(const SinkPrinter &s, T) {
return s;
}
template<uint64_t buffer_length = default_printer_buffer_length>
using BasicPrinter = HeapPrinter<BasicPrinterType, buffer_length>;
template<uint64_t buffer_length = default_printer_buffer_length>
using ErrorPrinter = HeapPrinter<ErrorPrinterType, buffer_length>;
template<uint64_t buffer_length = default_printer_buffer_length>
using StringStreamPrinter = HeapPrinter<StringStreamPrinterType, buffer_length>;
using print = BasicPrinter<>;
using error = ErrorPrinter<>;
using stringstream = StringStreamPrinter<>;
#ifdef DEBUG_RUNTIME
using debug = BasicPrinter<>;
#else
using debug = SinkPrinter;
#endif
// A Printer that automatically reserves stack space for the printer buffer, rather than malloc.
// Note that this requires an explicit buffer_length, and it (generally) should be <= 256.
template<PrinterType printer_type, uint64_t buffer_length>
class StackPrinter : public PrinterBase {
char scratch[buffer_length];
public:
explicit StackPrinter(void *user_context_)
: PrinterBase(user_context_, scratch, buffer_length) {
static_assert(buffer_length <= 256, "StackPrinter is meant only for small buffer sizes; you are probably making a mistake.");
}
};
template<uint64_t buffer_length = default_printer_buffer_length>
using StackBasicPrinter = StackPrinter<BasicPrinterType, buffer_length>;
template<uint64_t buffer_length = default_printer_buffer_length>
using StackErrorPrinter = StackPrinter<ErrorPrinterType, buffer_length>;
template<uint64_t buffer_length = default_printer_buffer_length>
using StackStringStreamPrinter = StackPrinter<StringStreamPrinterType, buffer_length>;
} // namespace
} // namespace Internal
} // namespace Runtime
} // namespace Halide
#endif