GCC Code Coverage Report


Directory: csrc/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 65.8% 173 / 0 / 263
Functions: 66.7% 2 / 0 / 3
Branches: 44.5% 49 / 0 / 110

evt3.c
Line Branch Exec Source
1
2 #include "evutils/evt3.h"
3 #include "evutils/types.h"
4 #include "evutils/parser.h"
5
6 #include <stdio.h>
7 #include <stdint.h>
8
9 #define EVT3_get_packet_type(packet) (((packet) >> 12) & 0xF)
10 #define EVT3_get_packet_data(packet) ((packet) & 0x0FFF)
11
12 enum EVT3_PacketType {
13 EVT3_EVT_ADDR_Y = 0x0,
14 EVT3_EVT_ADDR_X = 0x2,
15 EVT3_VECT_BASE_X = 0x3,
16 EVT3_VECT_12 = 0x4,
17 EVT3_VECT_8 = 0x5,
18 EVT3_EVT_TIME_LOW = 0x6,
19 EVT3_CONTINUED_4 = 0x7,
20 EVT3_EVT_TIME_HIGH = 0x8,
21 EVT3_EXT_TRIGGER = 0xA,
22 EVT3_OTHERS = 0xE,
23 EVT3_CONTINUED_12 = 0xF
24 };
25
26
27 typedef struct evt3_state_s {
28 uint64_t ts_high_high;
29 uint64_t ts_high;
30 uint64_t ts_low;
31 uint64_t ts;
32
33 uint16_t y;
34 uint16_t vecbase_x;
35 uint8_t vecbase_p;
36 } evt3_state_t;
37
38
39
40 189 size_t EVT3_state_size(void) {
41 189 return sizeof(evt3_state_t);
42 }
43
44
45 #define EMIT_SOA() do { \
46 unsigned has = (vec_valid != 0u); \
47 uint32_t lz = (uint32_t)__builtin_ctz(vec_valid | 0x80000000u); \
48 out_ts[n] = ts; \
49 out_x[n] = (uint16_t)(bx + lz); \
50 out_y[n] = y; \
51 out_p[n] = p; \
52 n += has; \
53 vec_valid &= vec_valid - 1u; \
54 } while (0)
55
56
57 // EVUTILS_UNALIGNED typedefs are provided in compat.h
58
59 __attribute__((always_inline))
60 static inline const unaligned_uint16_t * EVT3_parse_vector_12_12_8_soa(
61 const unaligned_uint16_t * __restrict__ current,
62 evt3_state_t * __restrict__ state,
63 timestamp_t* __restrict__ out_ts, uint16_t* __restrict__ out_x,
64 uint16_t* __restrict__ out_y, uint8_t* __restrict__ out_p,
65 size_t * n_events) {
66
67 46093684 uint32_t vec_valid = EVT3_get_packet_data(*current);
68 46093684 current++;
69
70
3/8
✓ Branch 0 taken 45615413 times.
✓ Branch 1 taken 186 times.
✓ Branch 2 taken 470516 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
46086115 if(likely(EVT3_get_packet_type(*current) == EVT3_VECT_12)) {
71 46090996 vec_valid |= (uint32_t)(EVT3_get_packet_data(*current) << 12);
72 46090996 current++;
73
74
6/16
✓ Branch 0 taken 45615396 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 4875 times.
✓ Branch 3 taken 153 times.
✓ Branch 4 taken 470516 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
46090996 if (likely(EVT3_get_packet_type(*current) == EVT3_VECT_8)) {
75 46090826 vec_valid |= (uint32_t)(*current & 0x00FF) << 24;
76 46090826 current++;
77 }
78 }
79 46093684 const timestamp_t ts = state->ts;
80 46093684 const uint16_t y = state->y;
81 46093684 const uint8_t p = state->vecbase_p;
82 46093684 const uint16_t bx = state->vecbase_x;
83 46093684 size_t n = *n_events;
84
85 46093684 EMIT_SOA(); EMIT_SOA(); EMIT_SOA(); EMIT_SOA(); /* 4 unconditional — no branch between them */
86
87
8/16
✓ Branch 0 taken 241506969 times.
✓ Branch 1 taken 45615599 times.
✓ Branch 2 taken 122951 times.
✓ Branch 3 taken 7530 times.
✓ Branch 4 taken 6565364 times.
✓ Branch 5 taken 470516 times.
✓ Branch 6 taken 995 times.
✓ Branch 7 taken 39 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
294289963 while (vec_valid) { EMIT_SOA(); } /* tail: only pop>4 (~23%) re-enters */
88
89 46093684 *n_events = n;
90 46093684 state->vecbase_x = bx + 32;
91
92 46093684 return current;
93 }
94
95
96
97 // TODO: For future performance optimization, implement dynamic CPU dispatch
98 // (e.g., using __attribute__((target_clones("avx2", "default"))) on GCC/Clang)
99 // or manual cpuid + function pointers to enable SIMD decoding on supported
100 // hardware without breaking universal portability on older CPUs.
101
102
103 #define EVT3_INPUT_PADDING 4
104 EVUTILS_TARGET_CLONES
105 4522 parser_result_t EVT3_parse_chunk_soa(
106 evt3_state_t *state,
107 const evt3_input_buffer_t *input_buffer,
108 event_buffer_soa_t *event_buffer,
109 trigger_buffer_soa_t *trigger_buffer) {
110
111 // Hoist variables for better optimization
112 4522 const unaligned_uint16_t *restrict current = (const unaligned_uint16_t *)input_buffer->begin;
113 4522 const unaligned_uint16_t * end_offset = (const unaligned_uint16_t *)(input_buffer->end - EVT3_INPUT_PADDING);
114
115 // Event output buffers
116 4522 const size_t events_capacity = event_buffer->capacity;
117 // Keep space for one full vector group; guard the size_t subtraction
118 // against tiny buffers (capacity <= 64 would wrap to a huge offset).
119 4522 const size_t events_capacity_offset = events_capacity > 64 ? events_capacity - 64 : 0;
120 4522 size_t n_events_read = event_buffer->size;
121
122 4522 timestamp_t* restrict out_ts = event_buffer->t;
123 4522 uint16_t* restrict out_x = event_buffer->x;
124 4522 uint16_t* restrict out_y = event_buffer->y;
125 4522 uint8_t* restrict out_p = event_buffer->p;
126
127 // Trigger output buffers
128 4522 const size_t triggers_capacity = trigger_buffer->capacity;
129 4522 size_t n_triggers_read = trigger_buffer->size;
130
131 4522 timestamp_t* restrict trigger_ts = trigger_buffer->t;
132 4522 uint8_t* restrict trigger_id = trigger_buffer->id;
133 4522 uint8_t* restrict trigger_p = trigger_buffer->p;
134
135 // State variables
136 4522 evt3_state_t local_state = *state;
137
138 4522 parse_status_t status = EVUTILS_PARSE_OK;
139
140
141 // Main parsing looop
142 4522 while(
143 477226428 current < end_offset &&
144
5/12
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 852 times.
✓ Branch 1 taken 477226428 times.
✓ Branch 2 taken 624 times.
✓ Branch 3 taken 477225804 times.
✓ Branch 4 taken 477225804 times.
✗ Branch 5 not taken.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
477227280 n_events_read < events_capacity_offset &&
145 n_triggers_read < triggers_capacity) {
146
147 477225804 uint32_t packet_type = EVT3_get_packet_type(*current);
148 477225804 uint32_t packet_data = EVT3_get_packet_data(*current);
149
150 // Most common case checks first
151
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 258976216 times.
✓ Branch 1 taken 218249588 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
477225804 if(packet_type == EVT3_EVT_ADDR_X) {
152 258976216 out_ts[n_events_read] = local_state.ts;
153 258976216 out_y[n_events_read] = local_state.y;
154 258976216 out_x[n_events_read] = (packet_data & 0x7FF);
155 258976216 out_p[n_events_read] = (packet_data & 0x800) >> 11;
156 258976216 n_events_read++;
157 258976216 current++;
158 258976216 continue;
159 }
160
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 30186390 times.
✓ Branch 1 taken 188063198 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
218249588 if(packet_type == EVT3_VECT_BASE_X) {
161 // Implementation omitted for brevity
162 30186390 local_state.vecbase_x = packet_data & 0x7FF;
163 30186390 local_state.vecbase_p = (packet_data & 0x800) >> 11;
164
165 30186390 current++;
166
167 // Parse vector messages
168
5/12
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 45615873 times.
✓ Branch 1 taken 30186116 times.
✓ Branch 2 taken 45615873 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45615599 times.
✓ Branch 5 taken 274 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
75801989 while (EVT3_get_packet_type(*current) == EVT3_VECT_12 && current < end_offset && n_events_read < events_capacity_offset) {
169 45615599 current = EVT3_parse_vector_12_12_8_soa(current, &local_state, out_ts, out_x, out_y, out_p, &n_events_read);
170 }
171
172 30186390 continue;
173 }
174
175
7/14
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 143037219 times.
✓ Branch 1 taken 6291614 times.
✓ Branch 2 taken 36778761 times.
✓ Branch 3 taken 3422 times.
✓ Branch 4 taken 7530 times.
✓ Branch 5 taken 3046 times.
✓ Branch 6 taken 1941606 times.
EVT3_parse_chunk_soa.default:
✗ 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.
188063198 switch(packet_type) {
176 143037219 case EVT3_EVT_ADDR_Y:
177 143037219 local_state.y = packet_data & 0x7FF;
178 143037219 break;
179 6291614 case EVT3_EVT_TIME_HIGH:
180 {
181 6291614 uint32_t new_ts_high = packet_data << 12;
182
183
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 2304 times.
✓ Branch 1 taken 6289310 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
6291614 if(unlikely(new_ts_high < local_state.ts_high)) {
184 2304 local_state.ts_high_high += 0x1000000;
185 }
186 6291614 local_state.ts_high = new_ts_high;
187
188 6291614 current++;
189 6291614 packet_type = EVT3_get_packet_type(*current);
190
191
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 1872369 times.
✓ Branch 1 taken 4419245 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
6291614 if(unlikely(packet_type != EVT3_EVT_TIME_LOW)) {
192 1872369 continue;
193 }
194 4419245 packet_data = EVT3_get_packet_data(*current);
195 }
196 __attribute__((fallthrough));
197 41198006 case EVT3_EVT_TIME_LOW:
198 41198006 local_state.ts_low = packet_data;
199 41198006 local_state.ts = local_state.ts_high_high | local_state.ts_high | local_state.ts_low;
200 41198006 break;
201 3422 case EVT3_EXT_TRIGGER:
202 3422 trigger_ts[n_triggers_read] = local_state.ts;
203 3422 trigger_id[n_triggers_read] = packet_data >> 8;
204 3422 trigger_p[n_triggers_read] = packet_data & 0x1;
205 3422 n_triggers_read++;
206 3422 break;
207
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 5028 times.
✓ Branch 1 taken 2502 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
7530 case EVT3_VECT_12:
208 7530 current = EVT3_parse_vector_12_12_8_soa(current, &local_state, out_ts, out_x, out_y, out_p, &n_events_read);
209 7530 continue;
210 break;
211 3046 case EVT3_VECT_8:
212 case EVT3_EVT_ADDR_X:
213 case EVT3_VECT_BASE_X:
214 // Unexpected packet in this position (corrupt/out-of-order
215 // stream): skip the offending word and stop so the caller can
216 // warn and resume. strict mode turns the warning into an error.
217 3046 status = EVUTILS_PARSE_WARNING;
218 3046 current++;
219 3046 goto parse_end;
220 1941606 case EVT3_OTHERS:
221 case EVT3_CONTINUED_12:
222 case EVT3_CONTINUED_4:
223 default:
224 1941606 break;
225 }
226 186180253 current++;
227 }
228 1476 parse_end:
229 4522 *state = local_state;
230
231 4522 event_buffer->size = n_events_read;
232 4522 trigger_buffer->size = n_triggers_read;
233
234 /* Report why parsing stopped: output space exhausted vs input drained. */
235
2/4
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 1476 times.
✓ Branch 1 taken 3046 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
4522 if (status != EVUTILS_PARSE_WARNING) {
236
3/8
EVT3_parse_chunk_soa.avx2:
✓ Branch 0 taken 852 times.
✓ Branch 1 taken 624 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 852 times.
EVT3_parse_chunk_soa.default:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1476 if (n_events_read >= events_capacity_offset || n_triggers_read >= triggers_capacity) {
237 624 status = EVUTILS_PARSE_OUTPUT_FULL;
238 }
239 }
240
241 4522 return (parser_result_t){
242 .current = (const void *)current,
243 .status = status
244 };
245 }
246
247
248 /* delta_t variant: a specialised copy of EVT3_parse_chunk_soa that stops as soon
249 * as the running timestamp reaches end_ts, so one call decodes exactly one time
250 * window (events with ts < end_ts) with no Python-side boundary search or
251 * overshoot carry. The n_events parser above is left untouched (and branch-free)
252 * -- only the top-of-loop timestamp guard and the shared vector sub-parser
253 * differ. Emits EVUTILS_PARSE_WINDOW_DONE when the window boundary is reached.
254 *
255 * The guard sits at the top of the loop because in EVT3 the timestamp only
256 * changes at TIME_LOW / TIME_HIGH words; every event (ADDR_X, VECT_*) copies the
257 * current local_state.ts, so once ts >= end_ts no further event can belong to
258 * this window. State is persisted, so the next call resumes at the exact word
259 * with the correct timestamp. */
260 EVUTILS_TARGET_CLONES
261 220 parser_result_t EVT3_parse_delta_t_soa(
262 evt3_state_t *state,
263 const evt3_input_buffer_t *input_buffer,
264 event_buffer_soa_t *event_buffer,
265 trigger_buffer_soa_t *trigger_buffer,
266 timestamp_t end_ts) {
267
268 220 parse_status_t status = EVUTILS_PARSE_OK;
269 220 const unaligned_uint16_t *restrict current = (const unaligned_uint16_t *)input_buffer->begin;
270 220 const unaligned_uint16_t * end_offset = (const unaligned_uint16_t *)(input_buffer->end - EVT3_INPUT_PADDING);
271
272 220 const size_t events_capacity = event_buffer->capacity;
273 220 const size_t events_capacity_offset = events_capacity > 64 ? events_capacity - 64 : 0;
274 220 size_t n_events_read = event_buffer->size;
275
276 220 timestamp_t* restrict out_ts = event_buffer->t;
277 220 uint16_t* restrict out_x = event_buffer->x;
278 220 uint16_t* restrict out_y = event_buffer->y;
279 220 uint8_t* restrict out_p = event_buffer->p;
280
281 220 const size_t triggers_capacity = trigger_buffer->capacity;
282 220 size_t n_triggers_read = trigger_buffer->size;
283
284 220 timestamp_t* restrict trigger_ts = trigger_buffer->t;
285 220 uint8_t* restrict trigger_id = trigger_buffer->id;
286 220 uint8_t* restrict trigger_p = trigger_buffer->p;
287
288 220 evt3_state_t local_state = *state;
289
290 220 while(
291 1506341 current < end_offset &&
292 1506362 n_events_read < events_capacity_offset &&
293 n_triggers_read < triggers_capacity) {
294
295 /* Time boundary reached: every remaining event would land in the next
296 * window. Stop here; state (incl. ts) is saved for the next call. */
297 1506332 if (local_state.ts >= end_ts) {
298 190 break;
299 }
300
301 1506142 __builtin_prefetch(current + 64, 0, 0);
302 1506142 uint32_t packet_type = EVT3_get_packet_type(*current);
303 1506142 uint32_t packet_data = EVT3_get_packet_data(*current);
304
305 1506142 if(packet_type == EVT3_EVT_ADDR_X) {
306 853326 out_ts[n_events_read] = local_state.ts;
307 853326 out_y[n_events_read] = local_state.y;
308 853326 out_x[n_events_read] = (packet_data & 0x7FF);
309 853326 out_p[n_events_read] = (packet_data & 0x800) >> 11;
310 853326 n_events_read++;
311 853326 current++;
312 853326 continue;
313 }
314 652816 if(packet_type == EVT3_VECT_BASE_X) {
315 147673 local_state.vecbase_x = packet_data & 0x7FF;
316 147673 local_state.vecbase_p = (packet_data & 0x800) >> 11;
317 147673 current++;
318 618189 while (EVT3_get_packet_type(*current) == EVT3_VECT_12 && current < end_offset && n_events_read < events_capacity_offset) {
319 470516 current = EVT3_parse_vector_12_12_8_soa(current, &local_state, out_ts, out_x, out_y, out_p, &n_events_read);
320 }
321 147673 continue;
322 }
323
324 505143 switch(packet_type) {
325 356639 case EVT3_EVT_ADDR_Y:
326 356639 local_state.y = packet_data & 0x7FF;
327 356639 break;
328 7202 case EVT3_EVT_TIME_HIGH:
329 {
330 7202 uint32_t new_ts_high = packet_data << 12;
331 7202 if(unlikely(new_ts_high < local_state.ts_high)) {
332 local_state.ts_high_high += 0x1000000;
333 }
334 7202 local_state.ts_high = new_ts_high;
335 7202 current++;
336 7202 packet_type = EVT3_get_packet_type(*current);
337 7202 if(unlikely(packet_type != EVT3_EVT_TIME_LOW)) {
338 809 continue;
339 }
340 6393 packet_data = EVT3_get_packet_data(*current);
341 }
342 __attribute__((fallthrough));
343 145703 case EVT3_EVT_TIME_LOW:
344 145703 local_state.ts_low = packet_data;
345 145703 local_state.ts = local_state.ts_high_high | local_state.ts_high | local_state.ts_low;
346 145703 break;
347 case EVT3_EXT_TRIGGER:
348 trigger_ts[n_triggers_read] = local_state.ts;
349 trigger_id[n_triggers_read] = packet_data >> 8;
350 trigger_p[n_triggers_read] = packet_data & 0x1;
351 n_triggers_read++;
352 break;
353 39 case EVT3_VECT_12:
354 39 current = EVT3_parse_vector_12_12_8_soa(current, &local_state, out_ts, out_x, out_y, out_p, &n_events_read);
355 39 continue;
356 break;
357 case EVT3_VECT_8:
358 case EVT3_EVT_ADDR_X:
359 case EVT3_VECT_BASE_X:
360 // Unexpected packet in this position (corrupt/out-of-order
361 // stream): skip the offending word and stop so the caller can
362 // warn and resume. strict mode turns the warning into an error.
363 status = EVUTILS_PARSE_WARNING;
364 current++;
365 goto parse_end_delta;
366 1953 case EVT3_OTHERS:
367 case EVT3_CONTINUED_12:
368 case EVT3_CONTINUED_4:
369 default:
370 1953 break;
371 }
372 504295 current++;
373 }
374
375 30 parse_end_delta:
376 220 *state = local_state;
377 220 event_buffer->size = n_events_read;
378 220 trigger_buffer->size = n_triggers_read;
379
380 220 if (status != EVUTILS_PARSE_WARNING) {
381 220 if (local_state.ts >= end_ts) {
382 190 status = EVUTILS_PARSE_WINDOW_DONE;
383 30 } else if (n_events_read >= events_capacity_offset || n_triggers_read >= triggers_capacity) {
384 9 status = EVUTILS_PARSE_OUTPUT_FULL;
385 } else {
386 21 status = EVUTILS_PARSE_OK; /* input drained for this call */
387 }
388 }
389
390 220 return (parser_result_t){
391 .current = (const void *)current,
392 .status = status
393 };
394 }
395
396