src/api/api_internal.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "brotensor/tensor.h" | ||
| 4 | #include "brotensor/ops.h" | ||
| 5 | #include "brotensor/runtime.h" | ||
| 6 | #include "embed/embed.h" | ||
| 7 | |||
| 8 | #include <cstdint> | ||
| 9 | #include <cstring> | ||
| 10 | #include <exception> | ||
| 11 | #include <initializer_list> | ||
| 12 | #include <string> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | namespace brotensor::api { | ||
| 16 | |||
| 17 | struct GpuTensorHandle { | ||
| 18 | brotensor::Tensor tensor; | ||
| 19 | std::vector<int64_t> shape; | ||
| 20 | }; | ||
| 21 | |||
| 22 | 10 | inline GpuTensorHandle* toHandle(void* ptr) { | |
| 23 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (!ptr) return nullptr; |
| 24 | 10 | return static_cast<GpuTensorHandle*>(ptr); | |
| 25 | 10 | } | |
| 26 | |||
| 27 | inline const GpuTensorHandle* constHandle(const void* ptr) { | ||
| 28 | if (!ptr) return nullptr; | ||
| 29 | return static_cast<const GpuTensorHandle*>(ptr); | ||
| 30 | } | ||
| 31 | |||
| 32 | 6050 | inline brotensor::Tensor* toTensor(void* ptr) { | |
| 33 |
1/2✓ Branch 0 taken 6050 times.
✗ Branch 1 not taken.
|
6050 | if (!ptr) return nullptr; |
| 34 | 6050 | return &static_cast<GpuTensorHandle*>(ptr)->tensor; | |
| 35 | 6050 | } | |
| 36 | |||
| 37 | inline const brotensor::Tensor* constTensor(const void* ptr) { | ||
| 38 | if (!ptr) return nullptr; | ||
| 39 | return &static_cast<const GpuTensorHandle*>(ptr)->tensor; | ||
| 40 | } | ||
| 41 | |||
| 42 | // The old QuickJS binding accepted "fp32"/"f32", "fp16"/"f16", "bf16", | ||
| 43 | // "int8"/"i8" (and the numeric Dtype enum, which js/tensor.js maps to the | ||
| 44 | // string before the call). | ||
| 45 | 1838 | inline brotensor::Dtype parseDtype(const char* dt) { | |
| 46 |
1/2✓ Branch 0 taken 1838 times.
✗ Branch 1 not taken.
|
1838 | if (!dt) return brotensor::Dtype::FP32; |
| 47 |
3/4✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 172 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1666 times.
|
1838 | if (std::strcmp(dt, "fp16") == 0 || std::strcmp(dt, "f16") == 0) return brotensor::Dtype::FP16; |
| 48 |
1/2✓ Branch 0 taken 1666 times.
✗ Branch 1 not taken.
|
1666 | if (std::strcmp(dt, "bf16") == 0) return brotensor::Dtype::BF16; |
| 49 |
3/4✓ Branch 0 taken 1648 times.
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1648 times.
|
1666 | if (std::strcmp(dt, "int8") == 0 || std::strcmp(dt, "i8") == 0) return brotensor::Dtype::INT8; |
| 50 |
3/4✓ Branch 0 taken 1594 times.
✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1594 times.
|
1648 | if (std::strcmp(dt, "int32") == 0 || std::strcmp(dt, "i32") == 0) return brotensor::Dtype::INT32; |
| 51 |
1/2✓ Branch 0 taken 1594 times.
✗ Branch 1 not taken.
|
1594 | if (std::strcmp(dt, "f64") == 0) return brotensor::Dtype::F64; |
| 52 | 1594 | return brotensor::Dtype::FP32; | |
| 53 | 1838 | } | |
| 54 | |||
| 55 | 54 | inline const char* dtypeToString(brotensor::Dtype dt) { | |
| 56 |
4/8✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
54 | switch (dt) { |
| 57 | 14 | case brotensor::Dtype::FP32: return "fp32"; | |
| 58 | 18 | case brotensor::Dtype::FP16: return "fp16"; | |
| 59 | ✗ | case brotensor::Dtype::BF16: return "bf16"; | |
| 60 | 2 | case brotensor::Dtype::INT8: return "int8"; | |
| 61 | 20 | case brotensor::Dtype::INT32: return "int32"; | |
| 62 | ✗ | case brotensor::Dtype::F64: return "f64"; | |
| 63 | ✗ | case brotensor::Dtype::FP8_E4M3: return "fp8_e4m3"; | |
| 64 | ✗ | default: return "unknown"; | |
| 65 | } | ||
| 66 | 54 | } | |
| 67 | |||
| 68 | // ---- error channel --------------------------------------------------------- | ||
| 69 | // | ||
| 70 | // A native is called directly from generated code, so a C++ exception must | ||
| 71 | // not escape it (bronze's fatal.h: those frames carry no unwind metadata). | ||
| 72 | // A native that fails records the message here instead; the JS wrapper reads | ||
| 73 | // it back through `__bro_native.tensor.takeError()` right after the call and | ||
| 74 | // throws. Per thread, like the realm the natives serve. | ||
| 75 | std::string& lastErrorSlot(); | ||
| 76 | 262 | inline void setError(const std::string& msg) { lastErrorSlot() = msg; } | |
| 77 | |||
| 78 | // ---- `dynamic` arguments --------------------------------------------------- | ||
| 79 | // | ||
| 80 | // Nullable tensor slots (mask|null, bias|null, ...) cross as `dynamic`: the | ||
| 81 | // raw Value bits. null / undefined give nullptr. js/tensor*.js checks | ||
| 82 | // `instanceof GpuTensor` before the call, so the handle data is ours. | ||
| 83 | 678 | inline brotensor::Tensor* tensorFromValue(uint64_t bits) { | |
| 84 | 678 | void* d = bronze::embed::handleData(bronze::Value{bits}); | |
| 85 |
2/2✓ Branch 0 taken 176 times.
✓ Branch 1 taken 502 times.
|
678 | return d ? toTensor(d) : nullptr; |
| 86 | } | ||
| 87 | |||
| 88 | // The `const float* d_mask` convention of the ops: the device pointer of an | ||
| 89 | // optional FP32 GpuTensor, or nullptr. | ||
| 90 | 74 | inline const float* maskPtr(uint64_t bits) { | |
| 91 | 74 | auto* t = tensorFromValue(bits); | |
| 92 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 60 times.
|
74 | return t ? static_cast<const float*>(t->data) : nullptr; |
| 93 | } | ||
| 94 | |||
| 95 | // A JS array of GpuTensors (concatRows parts, ...) as tensor pointers. False | ||
| 96 | // with the error set when an element is not a GpuTensor. | ||
| 97 | bool readTensorArray(uint64_t bits, std::vector<brotensor::Tensor*>& out, const char* label); | ||
| 98 | |||
| 99 | // True when every pointer is non-null; otherwise records "<label>: GpuTensor | ||
| 100 | // argument missing" and answers false, so a body can `if (!need(...)) return;`. | ||
| 101 | 910 | inline bool need(const char* label, std::initializer_list<const void*> ptrs) { | |
| 102 |
2/2✓ Branch 0 taken 3136 times.
✓ Branch 1 taken 910 times.
|
4046 | for (const void* p : ptrs) { |
| 103 |
1/2✓ Branch 0 taken 3136 times.
✗ Branch 1 not taken.
|
3136 | if (!p) { |
| 104 | ✗ | setError(std::string(label) + ": GpuTensor argument missing"); | |
| 105 | ✗ | return false; | |
| 106 | } | ||
| 107 | } | ||
| 108 | 910 | return true; | |
| 109 | 910 | } | |
| 110 | |||
| 111 | // ---- input-size contract --------------------------------------------------- | ||
| 112 | // | ||
| 113 | // brotensor ops trust their caller's explicit dims: conv2d_forward reads | ||
| 114 | // N*C_in*H*W elements of X because the caller said so, whatever X holds, and | ||
| 115 | // an elementwise backward reads dY.size() == x.size() elements without | ||
| 116 | // checking. From C++ that is the library's contract; from JS it is an | ||
| 117 | // out-of-bounds read (or write) on the host or the device. So every native | ||
| 118 | // that hands brotensor a dim or a second operand checks the operand first. | ||
| 119 | // | ||
| 120 | // Outputs are exempt: the ops resize them. | ||
| 121 | |||
| 122 | // `t` holds at least `n` elements. `n` is computed in 64 bits by the caller | ||
| 123 | // (dims multiply past INT_MAX easily); a negative `n` means a negative dim. | ||
| 124 | 902 | inline bool needElems(const char* label, const char* name, | |
| 125 | const brotensor::Tensor* t, int64_t n) { | ||
| 126 |
1/2✓ Branch 0 taken 902 times.
✗ Branch 1 not taken.
|
902 | if (!t) { |
| 127 | ✗ | setError(std::string(label) + ": " + name + " must be a GpuTensor"); | |
| 128 | ✗ | return false; | |
| 129 | } | ||
| 130 |
1/2✓ Branch 0 taken 902 times.
✗ Branch 1 not taken.
|
902 | if (n < 0) { |
| 131 | ✗ | setError(std::string(label) + ": negative dimension for " + name); | |
| 132 | ✗ | return false; | |
| 133 | } | ||
| 134 |
5/6✓ Branch 0 taken 798 times.
✓ Branch 1 taken 104 times.
✓ Branch 2 taken 786 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 786 times.
✗ Branch 5 not taken.
|
902 | if (static_cast<int64_t>(t->size()) < n || (n > 0 && t->data == nullptr)) { |
| 135 |
8/16✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 104 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 104 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 104 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 104 times.
✗ Branch 15 not taken.
|
208 | setError(std::string(label) + ": " + name + " holds " + std::to_string(t->size()) + |
| 136 |
2/4✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
|
104 | " elements, the dims need " + std::to_string(n)); |
| 137 | 104 | return false; | |
| 138 | } | ||
| 139 | 798 | return true; | |
| 140 | 902 | } | |
| 141 | |||
| 142 | // `t` has exactly `ref`'s element count (the elementwise-pair case). | ||
| 143 | 110 | inline bool needSameSize(const char* label, const char* name, | |
| 144 | const brotensor::Tensor* t, const brotensor::Tensor* ref) { | ||
| 145 |
2/4✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 110 times.
|
110 | if (!t || !ref) { |
| 146 | ✗ | setError(std::string(label) + ": " + name + " must be a GpuTensor"); | |
| 147 | ✗ | return false; | |
| 148 | } | ||
| 149 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 76 times.
|
110 | if (t->size() != ref->size()) { |
| 150 |
8/16✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 34 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 34 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 34 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 34 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 34 times.
✗ Branch 15 not taken.
|
68 | setError(std::string(label) + ": " + name + " has " + std::to_string(t->size()) + |
| 151 |
3/6✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
|
34 | " elements, expected " + std::to_string(ref->size())); |
| 152 | 34 | return false; | |
| 153 | } | ||
| 154 | 76 | return true; | |
| 155 | 110 | } | |
| 156 | |||
| 157 | // `t` carries `ref`'s dtype (a kernel dispatches on one operand's dtype and | ||
| 158 | // reads the others with that element size). | ||
| 159 | 738 | inline bool needSameDtype(const char* label, const char* name, | |
| 160 | const brotensor::Tensor* t, const brotensor::Tensor* ref) { | ||
| 161 |
2/4✓ Branch 0 taken 738 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 738 times.
|
738 | if (!t || !ref) { |
| 162 | ✗ | setError(std::string(label) + ": " + name + " must be a GpuTensor"); | |
| 163 | ✗ | return false; | |
| 164 | } | ||
| 165 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 732 times.
|
738 | if (t->dtype != ref->dtype) { |
| 166 |
7/14✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
|
12 | setError(std::string(label) + ": " + name + " is " + dtypeToString(t->dtype) + |
| 167 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | ", expected " + dtypeToString(ref->dtype)); |
| 168 | 6 | return false; | |
| 169 | } | ||
| 170 | 732 | return true; | |
| 171 | 738 | } | |
| 172 | |||
| 173 | // Product of dims in 64 bits; negative when any dim is negative. | ||
| 174 | 278 | inline int64_t elems(std::initializer_list<int64_t> dims) { | |
| 175 | 278 | int64_t n = 1; | |
| 176 |
2/2✓ Branch 0 taken 852 times.
✓ Branch 1 taken 278 times.
|
1130 | for (int64_t d : dims) { |
| 177 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 852 times.
|
852 | if (d < 0) return -1; |
| 178 | 852 | n *= d; | |
| 179 | } | ||
| 180 | 278 | return n; | |
| 181 | 278 | } | |
| 182 | |||
| 183 | // An elementwise partner: same element count and dtype as `ref`. | ||
| 184 | 74 | inline bool needPair(const char* label, const char* name, | |
| 185 | const brotensor::Tensor* t, const brotensor::Tensor* ref) { | ||
| 186 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 52 times.
|
74 | return needSameSize(label, name, t, ref) && needSameDtype(label, name, t, ref); |
| 187 | } | ||
| 188 | |||
| 189 | // Each value is > 0 (kernel sizes, strides, dilations, groups, heads: a zero | ||
| 190 | // is a division by zero inside the op, which on the CPU backend is a crash). | ||
| 191 | 136 | inline bool needPositive(const char* label, std::initializer_list<int64_t> vals) { | |
| 192 |
2/2✓ Branch 0 taken 446 times.
✓ Branch 1 taken 132 times.
|
578 | for (int64_t v : vals) { |
| 193 |
2/2✓ Branch 0 taken 442 times.
✓ Branch 1 taken 4 times.
|
446 | if (v <= 0) { |
| 194 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | setError(std::string(label) + ": kernel/stride/dilation/group/head counts must be positive"); |
| 195 | 4 | return false; | |
| 196 | } | ||
| 197 | } | ||
| 198 | 132 | return true; | |
| 199 | 136 | } | |
| 200 | |||
| 201 | // Each value is >= 0 (dims, paddings, offsets). | ||
| 202 | 158 | inline bool needNonNegative(const char* label, std::initializer_list<int64_t> vals) { | |
| 203 |
2/2✓ Branch 0 taken 574 times.
✓ Branch 1 taken 156 times.
|
730 | for (int64_t v : vals) { |
| 204 |
2/2✓ Branch 0 taken 572 times.
✓ Branch 1 taken 2 times.
|
574 | if (v < 0) { |
| 205 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | setError(std::string(label) + ": dims, paddings and offsets must be non-negative"); |
| 206 | 2 | return false; | |
| 207 | } | ||
| 208 | } | ||
| 209 | 156 | return true; | |
| 210 | 158 | } | |
| 211 | |||
| 212 | // 2-D convolution geometry: positive kernel/stride/dilation/groups, groups | ||
| 213 | // dividing both channel counts, non-negative dims and paddings. Answers the | ||
| 214 | // output extent the op will produce (0 when the window does not fit). | ||
| 215 | 34 | inline bool convGeom2d(const char* label, int64_t C_in, int64_t C_out, int64_t H, int64_t W, | |
| 216 | int64_t kH, int64_t kW, int64_t sH, int64_t sW, | ||
| 217 | int64_t pH, int64_t pW, int64_t dH, int64_t dW, int64_t groups, | ||
| 218 | int64_t& H_out, int64_t& W_out) { | ||
| 219 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 32 times.
|
34 | if (!needPositive(label, {kH, kW, sH, sW, dH, dW, groups})) return false; |
| 220 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (!needNonNegative(label, {C_in, C_out, H, W, pH, pW})) return false; |
| 221 |
3/4✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 30 times.
|
32 | if (C_in % groups != 0 || C_out % groups != 0) { |
| 222 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | setError(std::string(label) + ": groups must divide C_in and C_out"); |
| 223 | 2 | return false; | |
| 224 | } | ||
| 225 | 30 | const int64_t spanH = H + 2 * pH - dH * (kH - 1) - 1; | |
| 226 | 30 | const int64_t spanW = W + 2 * pW - dW * (kW - 1) - 1; | |
| 227 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | H_out = spanH < 0 ? 0 : spanH / sH + 1; |
| 228 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | W_out = spanW < 0 ? 0 : spanW / sW + 1; |
| 229 | 30 | return true; | |
| 230 | 34 | } | |
| 231 | |||
| 232 | // One operand of a multi-tensor op: `t` must hold `n` elements. A null `t` | ||
| 233 | // is an absent optional operand and passes (need() has already rejected a | ||
| 234 | // missing required one). | ||
| 235 | struct Operand { | ||
| 236 | const char* name; | ||
| 237 | const brotensor::Tensor* t; | ||
| 238 | int64_t n; | ||
| 239 | }; | ||
| 240 | |||
| 241 | // Every operand holds its element count and, when `ref` is given, carries | ||
| 242 | // ref's dtype. | ||
| 243 | 126 | inline bool needOperands(const char* label, const brotensor::Tensor* ref, | |
| 244 | std::initializer_list<Operand> ops) { | ||
| 245 |
2/2✓ Branch 0 taken 118 times.
✓ Branch 1 taken 528 times.
|
646 | for (const Operand& o : ops) { |
| 246 |
2/2✓ Branch 0 taken 444 times.
✓ Branch 1 taken 84 times.
|
528 | if (!o.t) continue; |
| 247 |
2/2✓ Branch 0 taken 436 times.
✓ Branch 1 taken 8 times.
|
444 | if (!needElems(label, o.name, o.t, o.n)) return false; |
| 248 |
2/4✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 436 times.
✗ Branch 3 not taken.
|
436 | if (ref && !needSameDtype(label, o.name, o.t, ref)) return false; |
| 249 | } | ||
| 250 | 118 | return true; | |
| 251 | 126 | } | |
| 252 | |||
| 253 | // The ops' `const float* d_mask` operands (and the FP32 bias tables): an | ||
| 254 | // optional FP32 tensor of at least `n` elements. | ||
| 255 | 92 | inline bool needMask(const char* label, const brotensor::Tensor* mt, int64_t n, const char* name = "mask") { | |
| 256 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 60 times.
|
92 | if (!mt) return true; |
| 257 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 4 times.
|
32 | if (!needElems(label, name, mt, n)) return false; |
| 258 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
|
28 | if (mt->dtype != brotensor::Dtype::FP32) { |
| 259 | ✗ | setError(std::string(label) + ": " + name + " must be FP32"); | |
| 260 | ✗ | return false; | |
| 261 | } | ||
| 262 | 28 | return true; | |
| 263 | 92 | } | |
| 264 | |||
| 265 | // numHeads is positive and divides D. | ||
| 266 | 40 | inline bool needHeads(const char* label, int64_t D, int64_t numHeads) { | |
| 267 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (!needPositive(label, {numHeads})) return false; |
| 268 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 38 times.
|
40 | if (D % numHeads != 0) { |
| 269 |
6/12✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
6 | setError(std::string(label) + ": numHeads (" + std::to_string(numHeads) + ") must divide D (" + |
| 270 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | std::to_string(D) + ")"); |
| 271 | 2 | return false; | |
| 272 | } | ||
| 273 | 38 | return true; | |
| 274 | 40 | } | |
| 275 | |||
| 276 | // Path resolution for the file loaders (api.h setPathResolver). | ||
| 277 | std::string resolvePath(const std::string& path); | ||
| 278 | |||
| 279 | } // namespace brotensor::api | ||
| 280 | |||
| 281 | // Guard a native body that calls into brotensor: the ops throw | ||
| 282 | // std::runtime_error on a shape or dtype contract violation, and that has to | ||
| 283 | // become a recorded error, never an unwind through generated code. | ||
| 284 | #define BROTENSOR_API_TRY try { | ||
| 285 | #define BROTENSOR_API_CATCH(label) \ | ||
| 286 | } catch (const std::exception& e) { \ | ||
| 287 | ::brotensor::api::setError(std::string(label) + ": " + e.what()); \ | ||
| 288 | } | ||
| 289 |