GCC Code Coverage Report


Directory: brotensor/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 83.7% 251 / 0 / 300
Functions: 96.2% 25 / 0 / 26
Branches: 51.2% 103 / 0 / 201

src/jit/trace_dag.cpp
Line Branch Exec Source
1 #include "trace_dag.h"
2 #include <cstring>
3 #include <stdexcept>
4
5 namespace brotensor::jit {
6
7 ✗ const char* op_kind_name(TraceOpKind op) {
8 ✗ switch (op) {
9 ✗ case TraceOpKind::Input: return "Input";
10 ✗ case TraceOpKind::Add: return "Add";
11 ✗ case TraceOpKind::Sub: return "Sub";
12 ✗ case TraceOpKind::Mul: return "Mul";
13 ✗ case TraceOpKind::Div: return "Div";
14 ✗ case TraceOpKind::AddScalar: return "AddScalar";
15 ✗ case TraceOpKind::SubScalar: return "SubScalar";
16 ✗ case TraceOpKind::MulScalar: return "MulScalar";
17 ✗ case TraceOpKind::DivScalar: return "DivScalar";
18 ✗ case TraceOpKind::ScalarSub: return "ScalarSub";
19 ✗ case TraceOpKind::ScalarDiv: return "ScalarDiv";
20 ✗ case TraceOpKind::FMA: return "FMA";
21 ✗ case TraceOpKind::SiLU: return "SiLU";
22 ✗ case TraceOpKind::GELU: return "GELU";
23 ✗ case TraceOpKind::ReLU: return "ReLU";
24 ✗ case TraceOpKind::RMSNorm: return "RMSNorm";
25 ✗ case TraceOpKind::LayerNorm: return "LayerNorm";
26 ✗ case TraceOpKind::Modulate: return "Modulate";
27 ✗ case TraceOpKind::Tanh: return "Tanh";
28 ✗ case TraceOpKind::Sigmoid: return "Sigmoid";
29 ✗ case TraceOpKind::Copy: return "Copy";
30 ✗ case TraceOpKind::Where: return "Where";
31 ✗ case TraceOpKind::Clamp: return "Clamp";
32 ✗ case TraceOpKind::Cast: return "Cast";
33 }
34 ✗ return "Unknown";
35 ✗ }
36
37 97 BroadcastKind broadcast_of(int node_rows, int node_cols, int rows, int cols) {
38
3/6
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
97 if (node_rows == 1 && node_cols == 1 && rows * cols != 1) {
39 ✗ return BroadcastKind::Scalar;
40 }
41
4/6
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 81 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
97 if (node_rows == 1 && node_cols == cols && rows != 1) {
42 16 return BroadcastKind::Row;
43 }
44
4/6
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 76 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
81 if (node_rows == rows && node_cols == 1 && cols != 1) {
45 5 return BroadcastKind::Col;
46 }
47 // Anything else is addressed linearly. A node whose element count does not
48 // match the dominant shape is rejected by the compiler before it gets
49 // here (see trace_compiler_cuda.cpp's plan builder).
50 76 return BroadcastKind::Full;
51 97 }
52
53 4 bool TraceDAG::slot_matches(int slot, const Tensor& t) const {
54
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (slot < 0 || slot >= static_cast<int>(nodes_.size())) return false;
55 4 const TraceNode& n = nodes_[static_cast<std::size_t>(slot)];
56
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 return n.buffer == t.data && n.rows == t.rows && n.cols == t.cols &&
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 n.dtype == t.dtype && n.device == t.device;
58 4 }
59
60 37 int TraceDAG::register_input(const Tensor& t) {
61 37 auto it = input_ptr_to_slot_.find(t.data);
62
2/4
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
37 if (it != input_ptr_to_slot_.end() && slot_matches(it->second, t)) {
63 ✗ return it->second;
64 }
65
66 37 int id = static_cast<int>(nodes_.size());
67 37 TraceNode node;
68 37 node.id = id;
69 37 node.op = TraceOpKind::Input;
70 37 node.buffer = t.data;
71 37 node.device = t.device;
72 37 node.dtype = t.dtype;
73 37 node.rows = t.rows;
74 37 node.cols = t.cols;
75 37 node.is_inplace = false;
76 37 node.is_live_at_end = false;
77
78
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 nodes_.push_back(std::move(node));
79
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 input_ptr_to_slot_[t.data] = id;
80 37 return id;
81 37 }
82
83 72 int TraceDAG::add_node(TraceOpKind op, const std::vector<int>& inputs, float scalar,
84 void* buffer, const Device& dev, Dtype dt, int rows, int cols,
85 float scalar2) {
86 72 int id = static_cast<int>(nodes_.size());
87 72 TraceNode node;
88 72 node.id = id;
89 72 node.op = op;
90
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 node.inputs = inputs;
91 72 node.scalar = scalar;
92 72 node.scalar2 = scalar2;
93 72 node.buffer = buffer;
94 72 node.device = dev;
95 72 node.dtype = dt;
96 72 node.rows = rows;
97 72 node.cols = cols;
98 72 node.is_inplace = false;
99 72 node.is_live_at_end = false;
100
101
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 nodes_.push_back(std::move(node));
102 72 return id;
103 72 }
104
105 3 int TraceDAG::add_inplace_node(TraceOpKind op, int target_slot, int arg_slot,
106 void* buffer, float scalar, float scalar2) {
107 3 int id = static_cast<int>(nodes_.size());
108 3 const auto& target_node = nodes_.at(target_slot);
109
110 3 TraceNode node;
111 3 node.id = id;
112 3 node.op = op;
113
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (arg_slot >= 0) {
114
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 node.inputs = {target_slot, arg_slot};
115 2 } else {
116
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 node.inputs = {target_slot};
117 }
118 3 node.scalar = scalar;
119 3 node.scalar2 = scalar2;
120 3 node.buffer = buffer;
121 3 node.device = target_node.device;
122 3 node.dtype = target_node.dtype;
123 3 node.rows = target_node.rows;
124 3 node.cols = target_node.cols;
125 3 node.is_inplace = true;
126 3 node.inplace_target_slot = target_slot;
127 3 node.is_live_at_end = true;
128
129
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 nodes_.push_back(std::move(node));
130 3 return id;
131 3 }
132
133 11 void TraceDAG::analyze_liveness() {
134
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 for (auto& n : nodes_) {
135 68 n.outputs.clear();
136 }
137
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 for (const auto& n : nodes_) {
138
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 68 times.
127 for (int in_id : n.inputs) {
139
2/4
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 59 times.
59 if (in_id >= 0 && in_id < static_cast<int>(nodes_.size())) {
140 59 nodes_[in_id].outputs.push_back(n.id);
141 59 }
142 }
143 }
144
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 for (auto& n : nodes_) {
145
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 34 times.
68 if (n.op == TraceOpKind::Input) {
146 34 n.is_live_at_end = false;
147
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 31 times.
68 } else if (n.is_inplace) {
148 3 n.is_live_at_end = true;
149
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 21 times.
34 } else if (n.outputs.empty()) {
150 10 n.is_live_at_end = true;
151 10 } else {
152 21 n.is_live_at_end = false;
153 }
154 }
155 11 }
156
157 20 void TraceDAG::dominant_shape(int& rows, int& cols) const {
158 20 rows = 0;
159 20 cols = 0;
160 20 int64_t best = 0;
161
2/2
✓ Branch 0 taken 118 times.
✓ Branch 1 taken 20 times.
138 for (const auto& n : nodes_) {
162 118 const int64_t numel = static_cast<int64_t>(n.rows) * static_cast<int64_t>(n.cols);
163
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 20 times.
118 if (numel > best) {
164 20 best = numel;
165 20 rows = n.rows;
166 20 cols = n.cols;
167 20 }
168 }
169 20 }
170
171 11 uint64_t TraceDAG::compute_hash() const {
172 // 64-bit FNV-1a hash
173 11 uint64_t hash = 14695981039346656037ULL;
174 829 auto hash_val = [&hash](uint64_t val) {
175 818 hash ^= val;
176 818 hash *= 1099511628211ULL;
177 818 };
178
179 11 hash_val(nodes_.size());
180
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 for (const auto& n : nodes_) {
181 68 hash_val(static_cast<uint64_t>(n.op));
182 68 hash_val(static_cast<uint64_t>(n.device.type));
183 68 hash_val(static_cast<uint64_t>(n.device.index));
184 68 hash_val(static_cast<uint64_t>(n.dtype));
185 68 hash_val(static_cast<uint64_t>(n.rows));
186 68 hash_val(static_cast<uint64_t>(n.cols));
187 68 hash_val(n.is_inplace ? 1ULL : 0ULL);
188 68 hash_val(n.is_live_at_end ? 1ULL : 0ULL);
189
190 68 uint32_t scalar_bits = 0;
191 68 std::memcpy(&scalar_bits, &n.scalar, sizeof(float));
192 68 hash_val(scalar_bits);
193
194 68 uint32_t scalar2_bits = 0;
195 68 std::memcpy(&scalar2_bits, &n.scalar2, sizeof(float));
196 68 hash_val(scalar2_bits);
197
198 68 hash_val(n.inputs.size());
199
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 68 times.
127 for (int in_id : n.inputs) {
200 59 hash_val(static_cast<uint64_t>(in_id));
201 }
202 }
203 11 return hash;
204 }
205
206 15 void TraceDAG::reset() {
207 15 nodes_.clear();
208 15 input_ptr_to_slot_.clear();
209 15 }
210
211 123 TraceContext& TraceContext::current() {
212
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 1 time.
123 static thread_local TraceContext instance;
213 123 return instance;
214 }
215
216 namespace {
217
218 // The hook Tensor calls from its move ctor / move assignment / destructor.
219 // Installed by begin(), which is the earliest point at which a symbolic
220 // tensor can exist, so there is no static-initialisation order to get wrong.
221 23 void slot_retarget(int slot, const Tensor* from, Tensor* to) {
222 23 TraceContext::current().retarget(slot, from, to);
223 23 }
224
225 1 bool clamp_inplace_hook(Tensor& y, float lo, float hi) {
226 1 auto& ctx = TraceContext::current();
227
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (!ctx.is_active()) return false;
228 1 const int slot_y = ctx.get_or_register_slot(y);
229 1 ctx.record_inplace_op(TraceOpKind::Clamp, slot_y, -1, lo, y, hi);
230 1 return true;
231 1 }
232
233 3 bool cast_out_hook(const Tensor& src, Tensor& dst, Dtype out_dtype) {
234 3 auto& ctx = TraceContext::current();
235
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (!ctx.is_active()) return false;
236 1 const int slot_src = ctx.get_or_register_slot(src);
237 1 ctx.record_cast_out(slot_src, src, dst, out_dtype);
238 1 return true;
239 3 }
240
241 } // namespace
242
243 23 void TraceContext::retarget(int slot, const Tensor* from, Tensor* to) noexcept {
244
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 auto it = slot_to_tensor_.find(slot);
245 // A stale entry — the slot was already materialised or re-tracked — must
246 // not be clobbered by a tensor that no longer stands for it.
247
4/8
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 23 times.
23 if (it == slot_to_tensor_.end() || it->second != from) return;
248
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 22 times.
23 if (to) {
249
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 it->second = to;
250 1 } else {
251
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 slot_to_tensor_.erase(it);
252 }
253 23 }
254
255 26 void TraceContext::release_tracked_() noexcept {
256
5/8
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
28 for (auto& kv : slot_to_tensor_) {
257 2 Tensor* t = kv.second;
258
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!t) continue;
259 // It never owned anything, and its value was never written anywhere,
260 // so the honest result is an empty tensor rather than a shape with no
261 // storage behind it.
262 2 t->jit_slot = -1;
263 2 t->rows = 0;
264 2 t->cols = 0;
265 }
266 26 slot_to_tensor_.clear();
267 26 }
268
269 13 void TraceContext::begin() {
270
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (active_) {
271 ✗ throw std::runtime_error("brotensor::jit: Nested begin_trace() calls are not permitted");
272 }
273 13 detail::jit_slot_retarget = &slot_retarget;
274 13 detail::jit_clamp_inplace_hook = &clamp_inplace_hook;
275 13 detail::jit_cast_out_hook = &cast_out_hook;
276 13 dag_.reset();
277 13 active_ptr_to_slot_.clear();
278 13 release_tracked_();
279 13 active_ = true;
280 13 }
281
282 2 void TraceContext::discard() {
283 2 dag_.reset();
284 2 active_ptr_to_slot_.clear();
285 2 release_tracked_();
286 2 active_ = false;
287 2 }
288
289 11 void TraceContext::materialize_() {
290
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 11 times.
79 for (TraceNode& n : dag_.nodes()) {
291
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 55 times.
68 if (!n.is_live_at_end) continue;
292
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 9 times.
13 if (n.buffer != nullptr) continue; // caller already owns the storage
293
294 9 auto it = slot_to_tensor_.find(n.id);
295
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (it == slot_to_tensor_.end() || it->second == nullptr) {
296 // The caller dropped the Tensor, so nothing can read this value.
297 // Leave it an interior value of the fused kernel rather than
298 // allocating a buffer no one will look at.
299 ✗ n.is_live_at_end = false;
300 ✗ continue;
301 }
302
303 9 Tensor* held = it->second;
304 9 Tensor buf = Tensor::empty_on(n.device, n.rows, n.cols, n.dtype);
305
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (buf.data == nullptr) {
306 ✗ n.is_live_at_end = false;
307 ✗ continue;
308 }
309 // Drop the tracking first: the move assignment below must see an
310 // ordinary tensor on both sides, or it would retarget the slot it is
311 // in the middle of retiring.
312
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 slot_to_tensor_.erase(it);
313 9 held->jit_slot = -1;
314 9 *held = std::move(buf);
315 9 n.buffer = held->data;
316 9 }
317 11 }
318
319 11 TraceDAG TraceContext::end() {
320
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (!active_) {
321 ✗ throw std::runtime_error("brotensor::jit: end_trace() called without matching begin_trace()");
322 }
323 11 dag_.analyze_liveness();
324 11 materialize_();
325 11 active_ = false;
326 11 active_ptr_to_slot_.clear();
327 // Whatever is still tracked stands for a value the fused kernel only ever
328 // holds in registers; there is nothing to hand back.
329 11 release_tracked_();
330
331 11 bool any_output = false;
332
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 for (const TraceNode& n : dag_.nodes()) {
333
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 54 times.
65 if (n.is_live_at_end) { any_output = true; break; }
334 }
335
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (!any_output) {
336 ✗ dag_.reset();
337 ✗ throw std::runtime_error(
338 "brotensor::jit: the trace has no observable result — every value it "
339 "produced was dropped before end_trace(), so there is nothing for the "
340 "fused kernel to write. Keep the result Tensor alive across "
341 "end_trace(), store() it into a buffer you own, or write in place "
342 "into one of the inputs");
343 }
344 11 return std::move(dag_);
345 ✗ }
346
347 63 int TraceContext::get_or_register_slot(const Tensor& t) {
348 // A symbolic intermediate already *is* a node: it names its own slot, and
349 // it has no buffer to key on.
350
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 41 times.
63 if (t.jit_slot >= 0) return t.jit_slot;
351
352 // Everything else is caller-owned storage, keyed on its address. The
353 // address is stable for the trace's lifetime because the caller owns it —
354 // but a slot recorded earlier can still be stale if that tensor was freed
355 // and its address reused, so confirm the node still describes the tensor
356 // in hand before reusing its slot.
357 41 auto it = active_ptr_to_slot_.find(t.data);
358
4/4
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 37 times.
41 if (it != active_ptr_to_slot_.end() && dag_.slot_matches(it->second, t)) {
359 4 return it->second;
360 }
361 37 int slot = dag_.register_input(t);
362 37 active_ptr_to_slot_[t.data] = slot;
363 37 return slot;
364 63 }
365
366 33 Tensor TraceContext::record_op(TraceOpKind op, const std::vector<int>& in_slots,
367 float scalar, const Device& dev, Dtype dt,
368 int rows, int cols, float scalar2) {
369 66 const int slot = dag_.add_node(op, in_slots, scalar, /*buffer=*/nullptr,
370 33 dev, dt, rows, cols, scalar2);
371 33 Tensor out;
372 33 out.device = dev;
373 33 out.dtype = dt;
374 33 out.rows = rows;
375 33 out.cols = cols;
376 33 out.jit_slot = slot;
377 // With copy elision `out` is already the caller's object; without it, the
378 // move ctor retargets this entry onto wherever it lands.
379
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 slot_to_tensor_[slot] = &out;
380 33 return out;
381 33 }
382
383 1 void TraceContext::record_store(int src_slot, const Tensor& dst) {
384
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (dst.jit_slot >= 0) {
385 ✗ throw std::runtime_error(
386 "brotensor::jit: store() needs a destination the caller owns; its "
387 "target is a traced intermediate, which has no buffer to write to");
388 }
389
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 const int slot = dag_.add_node(TraceOpKind::Copy, {src_slot}, 0.0f, dst.data,
390 1 dst.device, dst.dtype, dst.rows, dst.cols);
391 1 active_ptr_to_slot_[dst.data] = slot;
392 1 }
393
394 1 void TraceContext::record_cast_out(int src_slot, const Tensor& src, Tensor& dst, Dtype out_dtype) {
395
2/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 if (dst.jit_slot < 0 && dst.data != nullptr && dst.device == src.device &&
396 ✗ dst.rows == src.rows && dst.cols == src.cols && dst.dtype == out_dtype) {
397 ✗ const int slot = dag_.add_node(TraceOpKind::Cast, {src_slot}, 0.0f, dst.data,
398 ✗ dst.device, out_dtype, dst.rows, dst.cols);
399 ✗ active_ptr_to_slot_[dst.data] = slot;
400 ✗ return;
401 }
402
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 dst = record_op(TraceOpKind::Cast, {src_slot}, 0.0f, src.device, out_dtype,
403 1 src.rows, src.cols);
404 1 }
405
406 3 int TraceContext::record_inplace_op(TraceOpKind op, int target_slot, int arg_slot,
407 float scalar, Tensor& target_tensor,
408 float scalar2) {
409 6 int slot = dag_.add_inplace_node(op, target_slot, arg_slot, target_tensor.data,
410 3 scalar, scalar2);
411
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (target_tensor.jit_slot >= 0) {
412 // In-place on a traced intermediate: it has no buffer, and from here
413 // on it stands for the node this op just produced.
414 ✗ slot_to_tensor_.erase(target_tensor.jit_slot);
415 ✗ target_tensor.jit_slot = slot;
416 ✗ slot_to_tensor_[slot] = &target_tensor;
417 ✗ } else {
418 3 active_ptr_to_slot_[target_tensor.data] = slot;
419 }
420 3 return slot;
421 }
422
423 } // namespace brotensor::jit
424