GCC Code Coverage Report


Directory: brotensor/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 65.5% 144 / 0 / 220
Functions: 79.2% 19 / 0 / 24
Branches: 21.6% 72 / 0 / 334

src/api/native_tensor_core.cpp
Line Branch Exec Source
1 #include "native_tensor_decl.h"
2 #include "api_internal.h"
3 #include <vector>
4
5 using namespace brotensor::api;
6
7 namespace {
8 thread_local std::vector<float> tl_download_f32;
9 thread_local std::vector<uint16_t> tl_download_u16;
10 thread_local std::vector<int8_t> tl_download_i8;
11 } // namespace
12
13 extern "C" {
14
15 ✗ void* bro_tensor_GpuTensor_ctor(void) {
16 ✗ return new GpuTensorHandle();
17 }
18
19 867 void bro_tensor_GpuTensor_dtor(void* self) {
20
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 867 times.
867 delete static_cast<GpuTensorHandle*>(self);
21 867 }
22
23 1724 void* bro_tensor_createTensor(int32_t rows, int32_t cols, const char* dtype) {
24 1724 auto* h = new GpuTensorHandle();
25 1724 auto dt = parseDtype(dtype);
26
3/4
✓ Branch 0 taken 1722 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1722 times.
1724 if (rows > 0 && cols > 0) {
27
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1722 times.
1722 if (static_cast<int64_t>(rows) * cols > INT32_MAX) {
28 ✗ setError("createTensor: rows*cols exceeds 2^31-1 elements");
29 ✗ return h;
30 }
31 BROTENSOR_API_TRY
32
2/4
✓ Branch 0 taken 1722 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1722 times.
✗ Branch 3 not taken.
1722 h->tensor = brotensor::Tensor::zeros_on(brotensor::default_device(), rows, cols, dt);
33
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
1722 BROTENSOR_API_CATCH("createTensor")
34 1722 }
35 1724 return h;
36 1724 }
37
38 160 int32_t bro_tensor_GpuTensor_rows_get(void* self) {
39 160 auto* t = toTensor(self);
40
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 return t ? t->rows : 0;
41 }
42
43 164 int32_t bro_tensor_GpuTensor_cols_get(void* self) {
44 164 auto* t = toTensor(self);
45
1/2
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
164 return t ? t->cols : 0;
46 }
47
48 14 int32_t bro_tensor_GpuTensor_size_get(void* self) {
49 14 auto* t = toTensor(self);
50
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 return t ? t->size() : 0;
51 }
52
53 ✗ int32_t bro_tensor_GpuTensor_bytes_get(void* self) {
54 ✗ auto* t = toTensor(self);
55 ✗ return t ? static_cast<int32_t>(t->bytes()) : 0;
56 }
57
58 10 void bro_tensor_GpuTensor_zero(void* self) {
59 10 auto* t = toTensor(self);
60
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
10 if (t && !t->empty()) {
61 BROTENSOR_API_TRY
62
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 t->zero();
63
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
10 BROTENSOR_API_CATCH("zero")
64 10 }
65 10 }
66
67 4 void bro_tensor_GpuTensor_resize(void* self, int32_t rows, int32_t cols, const char* dtype) {
68 4 auto* t = toTensor(self);
69
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (!t) return;
70 4 auto dt = parseDtype(dtype);
71
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (rows < 0 || cols < 0) {
72 ✗ setError("resize: negative dimension");
73 ✗ return;
74 }
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (static_cast<int64_t>(rows) * cols > INT32_MAX) {
76 ✗ setError("resize: rows*cols exceeds 2^31-1 elements");
77 ✗ return;
78 }
79 BROTENSOR_API_TRY
80
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 t->resize(rows, cols, dt);
81
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
4 BROTENSOR_API_CATCH("resize")
82 4 }
83
84 34 const char* bro_tensor_GpuTensor_dtype(void* self) {
85 34 auto* t = toTensor(self);
86
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 return t ? dtypeToString(t->dtype) : "fp32";
87 }
88
89 2 void* bro_tensor_GpuTensor_clone(void* self) {
90 2 auto* h = new GpuTensorHandle();
91 2 auto* src = toHandle(self);
92
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (src) {
93 BROTENSOR_API_TRY
94
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 h->tensor = src->tensor.clone();
95
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 h->shape = src->shape;
96
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH("clone")
97 2 }
98 2 return h;
99 ✗ }
100
101 // upload keeps the tensor's (rows, cols) when the data fills it exactly and
102 // otherwise becomes a (data_len, 1) column.
103 818 static bool uploadShape(const char* label, const brotensor::Tensor* t, uint32_t data_len, int& r, int& c) {
104
1/2
✓ Branch 0 taken 818 times.
✗ Branch 1 not taken.
818 if (data_len > static_cast<uint32_t>(INT32_MAX)) {
105 ✗ setError(std::string(label) + ": more than 2^31-1 elements");
106 ✗ return false;
107 }
108 818 r = t->rows;
109 818 c = t->cols;
110
1/2
✓ Branch 0 taken 818 times.
✗ Branch 1 not taken.
818 if (static_cast<int64_t>(r) * c != static_cast<int64_t>(data_len)) {
111 ✗ r = static_cast<int>(data_len);
112 ✗ c = 1;
113 ✗ }
114 818 return true;
115 818 }
116
117 800 void bro_tensor_GpuTensor_upload(void* self, const float* data, uint32_t data_len) {
118 800 auto* t = toTensor(self);
119
2/4
✓ Branch 0 taken 800 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 800 times.
800 if (!t || !data) return;
120 800 int r = 0, c = 0;
121
1/2
✓ Branch 0 taken 800 times.
✗ Branch 1 not taken.
800 if (!uploadShape("upload", t, data_len, r, c)) return;
122 BROTENSOR_API_TRY
123
2/4
✓ Branch 0 taken 800 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 800 times.
✗ Branch 3 not taken.
800 *t = brotensor::Tensor::from_host_on(brotensor::default_device(), data, r, c);
124
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
800 BROTENSOR_API_CATCH("upload")
125 800 }
126
127 608 void bro_tensor_GpuTensor_download(void* self, bronze_native_buffer* out) {
128
1/2
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
608 if (!out) return;
129 608 auto* t = toTensor(self);
130
2/4
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 608 times.
608 if (!t || t->empty()) {
131 ✗ out->data = nullptr;
132 ✗ out->length = 0;
133 ✗ out->release = nullptr;
134 ✗ out->ctx = nullptr;
135 ✗ return;
136 }
137
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 530 times.
608 if (t->dtype == brotensor::Dtype::FP32) {
138 530 tl_download_f32.resize(static_cast<size_t>(t->size()));
139 530 t->copy_to_host(tl_download_f32.data());
140 530 out->data = tl_download_f32.data();
141 530 out->length = static_cast<uint32_t>(tl_download_f32.size());
142 530 } else {
143 // Convert on the host: the scratch tensor must live on the CPU too,
144 // or cast() sees a mixed-device pair (Tensor::empty follows the
145 // default device, CUDA once the backend is up).
146 try {
147
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 auto host_t = t->to(brotensor::Device::CPU);
148
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 brotensor::Tensor fp32_t = brotensor::Tensor::empty_on(brotensor::Device::CPU, t->rows, t->cols, brotensor::Dtype::FP32);
149
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 brotensor::cast(host_t, fp32_t, brotensor::Dtype::FP32);
150
2/4
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 78 times.
✗ Branch 3 not taken.
78 tl_download_f32.resize(static_cast<size_t>(fp32_t.size()));
151
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 fp32_t.copy_to_host(tl_download_f32.data());
152
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
78 } catch (const std::exception& e) {
153 ✗ tl_download_f32.clear();
154 ✗ setError(std::string("download: ") + e.what());
155 ✗ }
156 78 out->data = tl_download_f32.data();
157 78 out->length = static_cast<uint32_t>(tl_download_f32.size());
158 }
159 608 out->release = nullptr;
160 608 out->ctx = nullptr;
161 608 }
162
163 ✗ void bro_tensor_GpuTensor_uploadFp16(void* self, const uint16_t* data, uint32_t data_len) {
164 ✗ auto* t = toTensor(self);
165 ✗ if (!t || !data) return;
166 ✗ int r = 0, c = 0;
167 ✗ if (!uploadShape("uploadFp16", t, data_len, r, c)) return;
168 BROTENSOR_API_TRY
169 ✗ *t = brotensor::Tensor::from_host_fp16_on(brotensor::default_device(), data, r, c);
170 ✗ BROTENSOR_API_CATCH("uploadFp16")
171 ✗ }
172
173 18 void bro_tensor_GpuTensor_downloadFp16(void* self, bronze_native_buffer* out) {
174
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (!out) return;
175 18 auto* t = toTensor(self);
176
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (!t || t->empty()) {
177 ✗ out->data = nullptr;
178 ✗ out->length = 0;
179 ✗ out->release = nullptr;
180 ✗ out->ctx = nullptr;
181 ✗ return;
182 }
183
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (t->dtype == brotensor::Dtype::FP16) {
184 18 tl_download_u16.resize(static_cast<size_t>(t->size()));
185 18 t->copy_to_host_fp16(tl_download_u16.data());
186 18 out->data = tl_download_u16.data();
187 18 out->length = static_cast<uint32_t>(tl_download_u16.size());
188 18 } else {
189 try {
190 ✗ auto host_t = t->to(brotensor::Device::CPU);
191 ✗ brotensor::Tensor fp16_t = brotensor::Tensor::empty_on(brotensor::Device::CPU, t->rows, t->cols, brotensor::Dtype::FP16);
192 ✗ brotensor::cast(host_t, fp16_t, brotensor::Dtype::FP16);
193 ✗ tl_download_u16.resize(static_cast<size_t>(fp16_t.size()));
194 ✗ fp16_t.copy_to_host_fp16(tl_download_u16.data());
195 ✗ } catch (const std::exception& e) {
196 ✗ tl_download_u16.clear();
197 ✗ setError(std::string("downloadFp16: ") + e.what());
198 ✗ }
199 ✗ out->data = tl_download_u16.data();
200 ✗ out->length = static_cast<uint32_t>(tl_download_u16.size());
201 }
202 18 out->release = nullptr;
203 18 out->ctx = nullptr;
204 18 }
205
206 18 void bro_tensor_GpuTensor_uploadInt8(void* self, const int8_t* data, uint32_t data_len) {
207 18 auto* t = toTensor(self);
208
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (!t || !data) return;
209 18 int r = 0, c = 0;
210
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (!uploadShape("uploadInt8", t, data_len, r, c)) return;
211 BROTENSOR_API_TRY
212
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 *t = brotensor::Tensor::from_host_int8_on(brotensor::default_device(), data, r, c);
213
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
18 BROTENSOR_API_CATCH("uploadInt8")
214 18 }
215
216 2 void bro_tensor_GpuTensor_downloadInt8(void* self, bronze_native_buffer* out) {
217
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!out) return;
218 2 auto* t = toTensor(self);
219
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!t || t->empty()) {
220 ✗ out->data = nullptr;
221 ✗ out->length = 0;
222 ✗ out->release = nullptr;
223 ✗ out->ctx = nullptr;
224 ✗ return;
225 }
226 try {
227
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 auto host_t = t->to(brotensor::Device::CPU);
228
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 tl_download_i8.resize(host_t.bytes());
229
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 std::memcpy(tl_download_i8.data(), host_t.host_raw(), host_t.bytes());
230
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2 } catch (const std::exception& e) {
231 ✗ tl_download_i8.clear();
232 ✗ setError(std::string("downloadInt8: ") + e.what());
233 ✗ }
234 2 out->data = tl_download_i8.data();
235 2 out->length = static_cast<uint32_t>(tl_download_i8.size());
236 2 out->release = nullptr;
237 2 out->ctx = nullptr;
238 2 }
239
240 4 bool bro_tensor_GpuTensor_downloadInto(void* self, float* dst, uint32_t dst_len) {
241 4 auto* t = toTensor(self);
242
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!t || !dst) {
243 ✗ setError("download(dst): not a GpuTensor");
244 ✗ return false;
245 }
246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (t->empty()) return true;
247 4 const auto n = static_cast<size_t>(t->size());
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (dst_len < n) {
249 ✗ setError("download(dst): dst holds " + std::to_string(dst_len) + " elements, tensor has " + std::to_string(n));
250 ✗ return false;
251 }
252 BROTENSOR_API_TRY
253
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (t->dtype == brotensor::Dtype::FP32) {
254
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 t->copy_to_host(dst);
255 4 } else {
256 ✗ auto host_t = t->to(brotensor::Device::CPU);
257 ✗ brotensor::Tensor fp32_t = brotensor::Tensor::empty_on(brotensor::Device::CPU, t->rows, t->cols, brotensor::Dtype::FP32);
258 ✗ brotensor::cast(host_t, fp32_t, brotensor::Dtype::FP32);
259 ✗ fp32_t.copy_to_host(dst);
260 ✗ }
261 4 return true;
262 ✗ BROTENSOR_API_CATCH("download(dst)")
263 ✗ return false;
264 4 }
265
266 4240 const char* bro_tensor_takeError(void) {
267 // The slot keeps the string alive until the next failure; the runtime
268 // copies a `str` return into a JS string before anything else runs.
269
2/2
✓ Branch 0 taken 4238 times.
✓ Branch 1 taken 2 times.
4240 static thread_local std::string taken;
270 4240 taken.swap(lastErrorSlot());
271 4240 lastErrorSlot().clear();
272 4240 return taken.c_str();
273 }
274
275 3452 bool bro_tensor_available_get(void) {
276 3452 return true;
277 }
278
279 // The old binding published a lowercase identifier ("cuda" | "metal" |
280 // "unknown"), and everything written against bro.tensor compares against one:
281 // js/tensor.js falls back to "cpu", the docs spell the values lowercase and
282 // every caller gates with `backend === "cpu"` / `!== "cpu"`. device_name()
283 // answers a display name ("CPU", "CUDA", "Metal", "CUDA:1"), so lower-case it
284 // here rather than hand JS a string no gate can match.
285 40 const char* bro_tensor_backend_get(void) {
286
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 2 times.
40 static thread_local std::string name;
287 40 name = brotensor::device_name(brotensor::default_device());
288
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 200 times.
240 for (char& c : name) {
289
3/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✓ Branch 3 taken 40 times.
200 if (c >= 'A' && c <= 'Z') c = static_cast<char>(c + 32);
290 }
291 40 return name.c_str();
292 }
293
294 ✗ void bro_tensor_init(void) {
295 BROTENSOR_API_TRY
296 ✗ brotensor::init();
297 ✗ BROTENSOR_API_CATCH("init")
298 ✗ }
299
300 ✗ void bro_tensor_sync(void) {
301 BROTENSOR_API_TRY
302 ✗ brotensor::sync_all();
303 ✗ BROTENSOR_API_CATCH("sync")
304 ✗ }
305
306 } // extern "C"
307