| 159 | |
| 160 | template <typename Compare> |
| 161 | inline void das_stable_index_r(void * base, size_t nel, size_t width, Compare cmp) { |
| 162 | if (nel <= 1) return; |
| 163 | unsigned char * a = static_cast<unsigned char *>(base); |
| 164 | std::vector<uint32_t> perm(nel); |
| 165 | for (uint32_t k = 0; k < uint32_t(nel); k++) perm[k] = k; |
| 166 | |
| 167 | das_qsort_r(perm.data(), nel, sizeof(uint32_t), [&](const void * pa, const void * pb) { |
| 168 | uint32_t ia = *static_cast<const uint32_t *>(pa); |
| 169 | uint32_t ib = *static_cast<const uint32_t *>(pb); |
| 170 | const unsigned char * ea = a + size_t(ia) * width; |
| 171 | const unsigned char * eb = a + size_t(ib) * width; |
| 172 | if (cmp(ea, eb)) return true; |
| 173 | if (cmp(eb, ea)) return false; |
| 174 | return ia < ib; // unique tiebreak → stable order |
| 175 | }); |
| 176 | |
| 177 | // Apply gather permutation final[k] = orig[perm[k]] in place. Each cycle reads each |
| 178 | // source exactly once before it is overwritten; orig[k] is parked in tmp. |
| 179 | std::vector<unsigned char> tmp(width); |
| 180 | const uint32_t DONE = 0xFFFFFFFFu; |
| 181 | for (uint32_t k = 0; k < uint32_t(nel); k++) { |
| 182 | if (perm[k] == DONE || perm[k] == k) { perm[k] = DONE; continue; } |
| 183 | sized_memcpy(tmp.data(), a + size_t(k)*width, width); // park orig[k] |
| 184 | uint32_t cur = k; |
| 185 | for (;;) { |
| 186 | uint32_t src = perm[cur]; |
| 187 | if (src == k) { sized_memcpy(a + size_t(cur)*width, tmp.data(), width); perm[cur] = DONE; break; } |
| 188 | sized_memcpy(a + size_t(cur)*width, a + size_t(src)*width, width); |
| 189 | perm[cur] = DONE; |
| 190 | cur = src; |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | // ============================================================================ |
| 196 | // Verification — the gold standard. Checks ALL of: |
no test coverage detected