LCOV - code coverage report
Current view: top level - src/v2 - v2_tails.c (source / functions) Coverage Total Hit
Test: lcov.info Lines: 86.8 % 91 79
Test Date: 2026-09-20 15:19:17 Functions: 100.0 % 9 9
Branches: 81.1 % 74 60

             Branch data     Line data    Source code
       1                 :             : /**
       2                 :             :  * @file v2_tails.c
       3                 :             :  * @brief Suffix-merged tail pool — format v2 §8.1.
       4                 :             :  *
       5                 :             :  * Copyright (c) 2026 M. A. Chatterjee <deftio at deftio dot com>
       6                 :             :  * BSD-2-Clause — see LICENSE.txt
       7                 :             :  */
       8                 :             : 
       9                 :             : #include "triepack/v2_tails.h"
      10                 :             : 
      11                 :             : #include <stdlib.h>
      12                 :             : #include <string.h>
      13                 :             : 
      14                 :             : typedef struct {
      15                 :             :     const uint8_t *bytes;
      16                 :             :     uint32_t len;
      17                 :             :     uint64_t offset;      /* assigned during emit */
      18                 :             :     uint64_t seq;         /* index in the caller's array, to map refs back */
      19                 :             :     uint64_t distinct_of; /* index into the distinct table */
      20                 :             : } entry;
      21                 :             : 
      22                 :             : struct tp2_tailpool {
      23                 :             :     uint8_t *pool;
      24                 :             :     uint64_t pool_len;
      25                 :             :     uint64_t distinct;
      26                 :             :     tp2_tail_ref *refs; /* one per input tail, in input order */
      27                 :             :     uint64_t count;
      28                 :             : };
      29                 :             : 
      30                 :             : /* §8.1 step 2: order by reversed byte sequence, ascending — compare the last
      31                 :             :    byte first. Bytes compare as *unsigned*; a signed char here would sort 0x80
      32                 :             :    below 0x01 and silently reorder the pool for any non-ASCII data. */
      33                 :        8858 : static int cmp_reversed(const void *a, const void *b)
      34                 :             : {
      35                 :        8858 :     const entry *x = a, *y = b;
      36                 :        8858 :     uint32_t i = 0;
      37   [ +  +  +  + ]:       40723 :     while (i < x->len && i < y->len) {
      38                 :       40356 :         uint8_t xb = x->bytes[x->len - 1 - i];
      39                 :       40356 :         uint8_t yb = y->bytes[y->len - 1 - i];
      40         [ +  + ]:       40356 :         if (xb != yb)
      41         [ +  + ]:        8491 :             return xb < yb ? -1 : 1;
      42                 :       31865 :         i++;
      43                 :             :     }
      44         [ +  + ]:         367 :     if (x->len != y->len)
      45         [ +  + ]:         365 :         return x->len < y->len ? -1 : 1;
      46                 :             :     /* Equal content: order by input position so the sort is total and the
      47                 :             :        result cannot depend on the sort implementation's stability. */
      48         [ -  + ]:           2 :     return (x->seq < y->seq) ? -1 : (x->seq > y->seq);
      49                 :             : }
      50                 :             : 
      51                 :        1026 : static int same_tail(const entry *a, const entry *b)
      52                 :             : {
      53   [ +  +  +  + ]:        1026 :     return a->len == b->len && memcmp(a->bytes, b->bytes, a->len) == 0;
      54                 :             : }
      55                 :             : 
      56                 :             : /* Is `s` a proper suffix of `big`? */
      57                 :        1024 : static int is_proper_suffix(const entry *s, const entry *big)
      58                 :             : {
      59   [ +  +  +  + ]:        1024 :     return s->len < big->len && memcmp(big->bytes + big->len - s->len, s->bytes, s->len) == 0;
      60                 :             : }
      61                 :             : 
      62                 :          14 : tp2_tailpool *tp2_tailpool_build(const uint8_t *const *tails, const uint32_t *lens, uint64_t count)
      63                 :             : {
      64                 :          14 :     tp2_tailpool *tp = calloc(1, sizeof(*tp));
      65         [ -  + ]:          14 :     if (!tp)
      66                 :           0 :         return NULL;
      67                 :          14 :     tp->count = count;
      68         [ +  + ]:          14 :     if (count == 0) {
      69                 :           1 :         tp->pool = calloc(1, 1);
      70         [ -  + ]:           1 :         return tp->pool ? tp : (free(tp), NULL);
      71                 :             :     }
      72                 :             : 
      73                 :          13 :     tp->refs = calloc((size_t)count, sizeof(*tp->refs));
      74                 :          13 :     entry *sorted = calloc((size_t)count, sizeof(*sorted));
      75   [ +  -  -  + ]:          13 :     if (!tp->refs || !sorted) {
      76                 :           0 :         free(sorted);
      77                 :           0 :         tp2_tailpool_destroy(&tp);
      78                 :           0 :         return NULL;
      79                 :             :     }
      80                 :             : 
      81         [ +  + ]:        1052 :     for (uint64_t i = 0; i < count; i++) {
      82                 :        1039 :         sorted[i].bytes = tails[i];
      83                 :        1039 :         sorted[i].len = lens[i];
      84                 :        1039 :         sorted[i].seq = i;
      85                 :             :     }
      86                 :             : 
      87                 :             :     /* Step 2: sort by reversed bytes. */
      88                 :          13 :     qsort(sorted, (size_t)count, sizeof(*sorted), cmp_reversed);
      89                 :             : 
      90                 :             :     /* Step 1 (completed): collapse to the *distinct* tails. Equal tails are
      91                 :             :        adjacent after the sort, so this is a single pass. */
      92                 :          13 :     uint64_t ndistinct = 0;
      93         [ +  + ]:        1052 :     for (uint64_t i = 0; i < count; i++) {
      94   [ +  +  +  + ]:        1039 :         if (i > 0 && same_tail(&sorted[i], &sorted[i - 1]))
      95                 :           2 :             sorted[i].distinct_of = ndistinct - 1;
      96                 :             :         else
      97                 :        1037 :             sorted[i].distinct_of = ndistinct++;
      98                 :             :     }
      99                 :             : 
     100                 :          13 :     entry *distinct = calloc((size_t)ndistinct, sizeof(*distinct));
     101         [ -  + ]:          13 :     if (!distinct) {
     102                 :           0 :         free(sorted);
     103                 :           0 :         tp2_tailpool_destroy(&tp);
     104                 :           0 :         return NULL;
     105                 :             :     }
     106         [ +  + ]:        1052 :     for (uint64_t i = 0; i < count; i++)
     107                 :        1039 :         distinct[sorted[i].distinct_of] = sorted[i];
     108                 :             : 
     109                 :             :     /* Worst case the pool is every distinct tail concatenated. */
     110                 :          13 :     uint64_t cap = 0;
     111         [ +  + ]:        1050 :     for (uint64_t i = 0; i < ndistinct; i++)
     112                 :        1037 :         cap += distinct[i].len;
     113         [ +  - ]:          13 :     tp->pool = calloc((size_t)(cap ? cap : 1), 1);
     114         [ -  + ]:          13 :     if (!tp->pool) {
     115                 :           0 :         free(sorted);
     116                 :           0 :         free(distinct);
     117                 :           0 :         tp2_tailpool_destroy(&tp);
     118                 :           0 :         return NULL;
     119                 :             :     }
     120                 :             : 
     121                 :             :     /* Step 3: walk from last to first, tracking the most recently emitted
     122                 :             :        tail. A tail that is a proper suffix of it costs no bytes. */
     123                 :          13 :     int64_t prev = -1;
     124         [ +  + ]:        1050 :     for (int64_t i = (int64_t)ndistinct - 1; i >= 0; i--) {
     125   [ +  +  +  + ]:        1037 :         if (prev >= 0 && is_proper_suffix(&distinct[i], &distinct[prev])) {
     126                 :         114 :             distinct[i].offset = distinct[prev].offset + distinct[prev].len - distinct[i].len;
     127                 :         114 :             continue;
     128                 :             :         }
     129                 :         923 :         distinct[i].offset = tp->pool_len;
     130                 :         923 :         memcpy(tp->pool + tp->pool_len, distinct[i].bytes, distinct[i].len);
     131                 :         923 :         tp->pool_len += distinct[i].len;
     132                 :         923 :         prev = i;
     133                 :             :     }
     134                 :             : 
     135                 :             :     /* Map every input tail back to its distinct entry's placement. */
     136         [ +  + ]:        1052 :     for (uint64_t i = 0; i < count; i++) {
     137                 :        1039 :         const entry *d = &distinct[sorted[i].distinct_of];
     138                 :        1039 :         tp->refs[sorted[i].seq].offset = d->offset;
     139                 :        1039 :         tp->refs[sorted[i].seq].length = d->len;
     140                 :             :     }
     141                 :             : 
     142                 :          13 :     tp->distinct = ndistinct;
     143                 :          13 :     free(sorted);
     144                 :          13 :     free(distinct);
     145                 :          13 :     return tp;
     146                 :             : }
     147                 :             : 
     148                 :          16 : void tp2_tailpool_destroy(tp2_tailpool **tp)
     149                 :             : {
     150   [ +  +  +  + ]:          16 :     if (!tp || !*tp)
     151                 :           2 :         return;
     152                 :          14 :     free((*tp)->pool);
     153                 :          14 :     free((*tp)->refs);
     154                 :          14 :     free(*tp);
     155                 :          14 :     *tp = NULL;
     156                 :             : }
     157                 :             : 
     158                 :          21 : const uint8_t *tp2_tailpool_bytes(const tp2_tailpool *tp)
     159                 :             : {
     160         [ +  - ]:          21 :     return tp ? tp->pool : NULL;
     161                 :             : }
     162                 :             : 
     163                 :        1046 : uint64_t tp2_tailpool_size(const tp2_tailpool *tp)
     164                 :             : {
     165         [ +  - ]:        1046 :     return tp ? tp->pool_len : 0;
     166                 :             : }
     167                 :             : 
     168                 :           4 : uint64_t tp2_tailpool_distinct(const tp2_tailpool *tp)
     169                 :             : {
     170         [ +  - ]:           4 :     return tp ? tp->distinct : 0;
     171                 :             : }
     172                 :             : 
     173                 :        1045 : tp2_tail_ref tp2_tailpool_ref(const tp2_tailpool *tp, uint64_t i)
     174                 :             : {
     175                 :        1045 :     tp2_tail_ref none = {0, 0};
     176   [ +  -  +  -  :        1045 :     if (!tp || !tp->refs || i >= tp->count)
                   -  + ]
     177                 :           0 :         return none;
     178                 :        1045 :     return tp->refs[i];
     179                 :             : }
        

Generated by: LCOV version 2.0-1