device/slang/debug.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/device/slang/debug.h" | ||
| 3 | |||
| 4 | #include <bit> | ||
| 5 | #include <cstring> | ||
| 6 | |||
| 7 | #include <fmt/format.h> | ||
| 8 | |||
| 9 | #include "chrono_batch_render/device/kernels/device.h" | ||
| 10 | #include "chrono_batch_render/util/log.h" | ||
| 11 | #include "chrono_batch_render/util/text.h" | ||
| 12 | |||
| 13 | namespace chrono::batch_render::device { | ||
| 14 | namespace { | ||
| 15 | |||
| 16 | constexpr size_t kShownFailures = 8; | ||
| 17 | |||
| 18 | ✗ | bool Done(uint32_t stamp, uint64_t finished) { | |
| 19 | ✗ | return stamp != 0 && int32_t(uint32_t(finished) - stamp) >= 0; | |
| 20 | } | ||
| 21 | |||
| 22 | ✗ | std::string Value(uint32_t bits, uint32_t type) { | |
| 23 | ✗ | switch (type) { | |
| 24 | ✗ | case kPrintedInt: | |
| 25 | ✗ | return std::to_string(int32_t(bits)); | |
| 26 | ✗ | case kPrintedFloat: | |
| 27 | ✗ | return fmt::format("{}", std::bit_cast<float>(bits)); | |
| 28 | ✗ | case kPrintedBool: | |
| 29 | ✗ | return bits != 0 ? "true" : "false"; | |
| 30 | ✗ | default: | |
| 31 | ✗ | return std::to_string(bits); | |
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | } | ||
| 36 | |||
| 37 | 40 | uint64_t DebugRecords::Bytes() { | |
| 38 | 40 | return uint64_t(kDebugHeaderWords + kCapacity * kDebugRecordWords) * 4; | |
| 39 | } | ||
| 40 | |||
| 41 | 20 | DebugRecords::DebugRecords(Ref<Buffer> buffer) : m_buffer(std::move(buffer)) { | |
| 42 | 20 | m_header = static_cast<uint32_t*>(m_buffer->Mapped()); | |
| 43 | 20 | std::memset(m_header, 0, Bytes()); | |
| 44 | 20 | } | |
| 45 | |||
| 46 | 80099 | BufferRange DebugRecords::Submission() const { | |
| 47 | 80099 | return m_buffer->Slice(uint64_t(kSubmissionWord) * 4, 4); | |
| 48 | } | ||
| 49 | |||
| 50 | 1117 | void DebugRecords::AddKernel(const std::string& kernel, std::span<const std::string> strings) { | |
| 51 | 1117 | const std::lock_guard lock(m_mutex); | |
| 52 | 1117 | m_kernels.try_emplace(SlangStringHash(kernel), kernel); | |
| 53 | 1117 | for (const std::string& text : strings) { | |
| 54 | ✗ | m_strings.try_emplace(SlangStringHash(text), text); | |
| 55 | } | ||
| 56 | 1117 | } | |
| 57 | |||
| 58 | ✗ | std::string DebugRecords::Format(const uint32_t* record) const { | |
| 59 | ✗ | const auto found = m_strings.find(record[kHashWord]); | |
| 60 | ✗ | std::string_view format = "{}"; | |
| 61 | ✗ | if (found != m_strings.end()) { | |
| 62 | ✗ | format = found->second; | |
| 63 | } | ||
| 64 | |||
| 65 | ✗ | const uint32_t values = (record[kKindWord] >> 8) & 0xff; | |
| 66 | ✗ | std::string text; | |
| 67 | ✗ | uint32_t next = 0; | |
| 68 | ✗ | for (size_t at = format.find("{}"); at != std::string_view::npos; at = format.find("{}")) { | |
| 69 | ✗ | text.append(format.substr(0, at)); | |
| 70 | ✗ | const uint32_t* value = record + kValuesWord + size_t(next) * 2; | |
| 71 | ✗ | text += next < values ? Value(value[0], value[1]) : "{}"; | |
| 72 | ✗ | ++next; | |
| 73 | ✗ | format.remove_prefix(at + 2); | |
| 74 | } | ||
| 75 | ✗ | text.append(format); | |
| 76 | ✗ | return text; | |
| 77 | ✗ | } | |
| 78 | |||
| 79 | ✗ | void DebugRecords::Report(const uint32_t* record, uint64_t finished) { | |
| 80 | ✗ | const auto found = m_kernels.find(record[kKernelWord]); | |
| 81 | ✗ | const std::string kernel = found != m_kernels.end() ? found->second : "a kernel"; | |
| 82 | ✗ | const std::string message = Format(record); | |
| 83 | ✗ | const uint32_t* grid = record + kThreadWord; | |
| 84 | ✗ | const std::string thread = | |
| 85 | ✗ | fmt::format("thread {} ({}, {}, {})", record[kLinearWord], grid[0], grid[1], grid[2]); | |
| 86 | |||
| 87 | ✗ | if ((record[kKindWord] & 0xff) == kAssertRecord) { | |
| 88 | ✗ | const uint64_t submission = (finished & ~uint64_t(0xffffffff)) | record[kStampWord]; | |
| 89 | ✗ | m_failures.emplace( | |
| 90 | ✗ | submission > finished ? submission - (uint64_t(1) << 32) : submission, | |
| 91 | ✗ | fmt::format("Assert failed in kernel {} at {}: {}", kernel, thread, message)); | |
| 92 | ✗ | } else if ((record[kKindWord] & 0xff) == kPrintRecord) { | |
| 93 | ✗ | Log(LogLevel::Info, "kernel {}, {}: {}", kernel, thread, message); | |
| 94 | } | ||
| 95 | ✗ | } | |
| 96 | |||
| 97 | 111868 | void DebugRecords::Read(uint64_t finished, bool idle) { | |
| 98 | 111868 | const std::lock_guard lock(m_mutex); | |
| 99 | 111868 | const uint32_t taken = m_header[kTakenWord]; | |
| 100 | 111868 | const uint32_t kept = std::min(taken, kCapacity); | |
| 101 | 111868 | for (; m_read < kept; ++m_read) { | |
| 102 | ✗ | uint32_t* record = m_header + kDebugHeaderWords + size_t(m_read) * kDebugRecordWords; | |
| 103 | ✗ | if (!Done(record[kStampWord], finished)) { | |
| 104 | break; | ||
| 105 | } | ||
| 106 | ✗ | Report(record, finished); | |
| 107 | ✗ | record[kStampWord] = 0; | |
| 108 | } | ||
| 109 | |||
| 110 | 111868 | if (!idle || m_read != kept) { | |
| 111 | 54438 | return; | |
| 112 | } | ||
| 113 | 57430 | if (taken > kCapacity) { | |
| 114 | ✗ | Log(LogLevel::Warning, | |
| 115 | "kernels wrote {} more asserts and prints than the {} the " | ||
| 116 | "debug buffer holds; those were dropped", | ||
| 117 | ✗ | taken - kCapacity, kCapacity); | |
| 118 | } | ||
| 119 | 57430 | m_header[kTakenWord] = 0; | |
| 120 | 57430 | m_read = 0; | |
| 121 | 111868 | } | |
| 122 | |||
| 123 | 30157 | std::optional<std::string> DebugRecords::TakeFailures(uint64_t submission) { | |
| 124 | 30157 | const std::lock_guard lock(m_mutex); | |
| 125 | 30157 | const auto end = m_failures.upper_bound(submission); | |
| 126 | 30157 | if (end == m_failures.begin()) { | |
| 127 | 30157 | return std::nullopt; | |
| 128 | } | ||
| 129 | |||
| 130 | ✗ | std::vector<std::string> messages; | |
| 131 | ✗ | size_t count = 0; | |
| 132 | ✗ | for (auto it = m_failures.begin(); it != end; ++it, ++count) { | |
| 133 | ✗ | if (count < kShownFailures) { | |
| 134 | ✗ | messages.push_back(it->second); | |
| 135 | } | ||
| 136 | } | ||
| 137 | ✗ | if (count > kShownFailures) { | |
| 138 | ✗ | messages.push_back(fmt::format("and {} more failed asserts", count - kShownFailures)); | |
| 139 | } | ||
| 140 | ✗ | m_failures.erase(m_failures.begin(), end); | |
| 141 | ✗ | return Join(messages, "\n"); | |
| 142 | 30157 | } | |
| 143 | |||
| 144 | 1124 | uint32_t SlangStringHash(std::string_view text) { | |
| 145 | 1124 | uint32_t hash = 0; | |
| 146 | 14906 | for (const char c : text) { | |
| 147 | 13782 | hash = uint32_t(uint8_t(c)) + (hash << 6) + (hash << 16) - hash; | |
| 148 | } | ||
| 149 | 1124 | return hash; | |
| 150 | } | ||
| 151 | |||
| 152 | } | ||
| 153 |