1 /* Copyright (C) 1995-2024 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
25 #include <sys/param.h>
28 # define STRING_TYPE char
29 # define USTRING_TYPE unsigned char
30 # define STRXFRM __strxfrm_l
31 # define STRLEN strlen
32 # define STPNCPY __stpncpy
33 # define WEIGHT_H "../locale/weight.h"
38 #define CONCAT(a,b) CONCAT1(a,b)
39 #define CONCAT1(a,b) a##b
41 /* Maximum string size that is calculated with cached indices. Right now this
42 is an arbitrary value open to optimizations. SMALL_STR_SIZE * 4 has to be
43 lower than __MAX_ALLOCA_CUTOFF. Keep localedata/xfrm-test.c in sync. */
44 #define SMALL_STR_SIZE 4095
46 #include "../locale/localeinfo.h"
49 /* Group locale data for shorter parameter lists. */
53 unsigned char *rulesets
;
54 USTRING_TYPE
*weights
;
60 #ifndef WIDE_CHAR_VERSION
62 /* We need UTF-8 encoding of numbers. */
64 utf8_encode (char *buf
, int val
)
77 for (step
= 2; step
< 6; ++step
)
78 if ((val
& (~(uint32_t)0 << (5 * step
+ 1))) == 0)
82 *buf
= (unsigned char) (~0xff >> step
);
86 buf
[step
] = 0x80 | (val
& 0x3f);
97 /* Find next weight and rule index. Inlined since called for every char. */
98 static __always_inline
size_t
99 find_idx (const USTRING_TYPE
**us
, int32_t *weight_idx
,
100 unsigned char *rule_idx
, const locale_data_t
*l_data
, const int pass
)
102 int32_t tmp
= findidx (l_data
->table
, l_data
->indirect
, l_data
->extra
, us
,
104 *rule_idx
= tmp
>> 24;
105 int32_t idx
= tmp
& 0xffffff;
106 size_t len
= l_data
->weights
[idx
++];
108 /* Skip over indices of previous levels. */
109 for (int i
= 0; i
< pass
; i
++)
112 len
= l_data
->weights
[idx
++];
120 find_position (const USTRING_TYPE
*us
, const locale_data_t
*l_data
,
124 unsigned char rule_idx
;
125 const USTRING_TYPE
*usrc
= us
;
127 find_idx (&usrc
, &weight_idx
, &rule_idx
, l_data
, pass
);
128 return l_data
->rulesets
[rule_idx
* l_data
->nrules
+ pass
] & sort_position
;
131 /* Do the transformation. */
133 do_xfrm (const USTRING_TYPE
*usrc
, STRING_TYPE
*dest
, size_t n
,
134 const locale_data_t
*l_data
)
137 unsigned char rule_idx
;
142 /* Now the passes over the weights. */
143 for (pass
= 0; pass
< l_data
->nrules
; ++pass
)
145 size_t backw_len
= 0;
146 last_needed
= needed
;
147 const USTRING_TYPE
*cur
= usrc
;
148 const USTRING_TYPE
*backw_start
= NULL
;
150 /* We assume that if a rule has defined `position' in one section
151 this is true for all of them. */
152 int position
= find_position (cur
, l_data
, pass
);
156 while (*cur
!= L('\0'))
158 const USTRING_TYPE
*pos
= cur
;
159 size_t len
= find_idx (&cur
, &weight_idx
, &rule_idx
, l_data
,
161 int rule
= l_data
->rulesets
[rule_idx
* l_data
->nrules
+ pass
];
163 if ((rule
& sort_forward
) != 0)
165 /* Handle the pushed backward sequence. */
166 if (backw_start
!= NULL
)
168 for (size_t i
= backw_len
; i
> 0; )
171 unsigned char rule_idx
;
172 size_t len
= find_idx (&backw_start
, &weight_idx
,
173 &rule_idx
, l_data
, pass
);
175 for (size_t j
= len
; j
> 0; j
--)
176 dest
[needed
+ i
- j
] =
177 l_data
->weights
[weight_idx
++];
187 /* Now handle the forward element. */
188 if (needed
+ len
< n
)
190 dest
[needed
++] = l_data
->weights
[weight_idx
++];
192 /* No more characters fit into the buffer. */
197 /* Remember start of the backward sequence & track length. */
198 if (backw_start
== NULL
)
205 /* Handle the pushed backward sequence. */
206 if (backw_start
!= NULL
)
208 for (size_t i
= backw_len
; i
> 0; )
210 size_t len
= find_idx (&backw_start
, &weight_idx
, &rule_idx
,
213 for (size_t j
= len
; j
> 0; j
--)
214 dest
[needed
+ i
- j
] =
215 l_data
->weights
[weight_idx
++];
226 #ifndef WIDE_CHAR_VERSION
232 while (*cur
!= L('\0'))
234 const USTRING_TYPE
*pos
= cur
;
235 size_t len
= find_idx (&cur
, &weight_idx
, &rule_idx
, l_data
,
237 int rule
= l_data
->rulesets
[rule_idx
* l_data
->nrules
+ pass
];
239 if ((rule
& sort_forward
) != 0)
241 /* Handle the pushed backward sequence. */
242 if (backw_start
!= NULL
)
244 for (size_t p
= backw_len
; p
> 0; p
--)
248 unsigned char rule_idx
;
249 const USTRING_TYPE
*backw_cur
= backw_start
;
251 /* To prevent a warning init the used vars. */
252 len
= find_idx (&backw_cur
, &weight_idx
,
253 &rule_idx
, l_data
, pass
);
255 for (i
= 1; i
< p
; i
++)
256 len
= find_idx (&backw_cur
, &weight_idx
,
257 &rule_idx
, l_data
, pass
);
261 #ifdef WIDE_CHAR_VERSION
262 if (needed
+ 1 + len
< n
)
265 for (i
= 0; i
< len
; ++i
)
266 dest
[needed
+ 1 + i
] =
267 l_data
->weights
[weight_idx
+ i
];
271 buflen
= utf8_encode (buf
, val
);
272 if (needed
+ buflen
+ len
< n
)
274 for (i
= 0; i
< buflen
; ++i
)
275 dest
[needed
+ i
] = buf
[i
];
276 for (i
= 0; i
< len
; ++i
)
277 dest
[needed
+ buflen
+ i
] =
278 l_data
->weights
[weight_idx
+ i
];
280 needed
+= buflen
+ len
;
292 /* Now handle the forward element. */
295 #ifdef WIDE_CHAR_VERSION
296 if (needed
+ 1 + len
< n
)
299 for (i
= 0; i
< len
; ++i
)
300 dest
[needed
+ 1 + i
] =
301 l_data
->weights
[weight_idx
+ i
];
305 buflen
= utf8_encode (buf
, val
);
306 if (needed
+ buflen
+ len
< n
)
308 for (i
= 0; i
< buflen
; ++i
)
309 dest
[needed
+ i
] = buf
[i
];
310 for (i
= 0; i
< len
; ++i
)
311 dest
[needed
+ buflen
+ i
] =
312 l_data
->weights
[weight_idx
+ i
];
314 needed
+= buflen
+ len
;
323 /* Remember start of the backward sequence & track length. */
324 if (backw_start
== NULL
)
330 /* Handle the pushed backward sequence. */
331 if (backw_start
!= NULL
)
333 for (size_t p
= backw_len
; p
> 0; p
--)
337 unsigned char rule_idx
;
338 const USTRING_TYPE
*backw_cur
= backw_start
;
340 /* To prevent a warning init the used vars. */
341 len
= find_idx (&backw_cur
, &weight_idx
,
342 &rule_idx
, l_data
, pass
);
344 for (i
= 1; i
< p
; i
++)
345 len
= find_idx (&backw_cur
, &weight_idx
,
346 &rule_idx
, l_data
, pass
);
350 #ifdef WIDE_CHAR_VERSION
351 if (needed
+ 1 + len
< n
)
354 for (i
= 0; i
< len
; ++i
)
355 dest
[needed
+ 1 + i
] =
356 l_data
->weights
[weight_idx
+ i
];
360 buflen
= utf8_encode (buf
, val
);
361 if (needed
+ buflen
+ len
< n
)
363 for (i
= 0; i
< buflen
; ++i
)
364 dest
[needed
+ i
] = buf
[i
];
365 for (i
= 0; i
< len
; ++i
)
366 dest
[needed
+ buflen
+ i
] =
367 l_data
->weights
[weight_idx
+ i
];
369 needed
+= buflen
+ len
;
379 /* Finally store the byte to separate the passes or terminate
382 dest
[needed
] = pass
+ 1 < l_data
->nrules
? L('\1') : L('\0');
386 /* This is a little optimization: many collation specifications have
387 a `position' rule at the end and if no non-ignored character
388 is found the last \1 byte is immediately followed by a \0 byte
389 signalling this. We can avoid the \1 byte(s). */
390 if (needed
> 2 && needed
== last_needed
+ 1)
392 /* Remove the \1 byte. */
394 dest
[needed
- 1] = L('\0');
397 /* Return the number of bytes/words we need, but don't count the NUL
398 byte/word at the end. */
402 /* Do the transformation using weight-index and rule cache. */
404 do_xfrm_cached (STRING_TYPE
*dest
, size_t n
, const locale_data_t
*l_data
,
405 size_t idxmax
, int32_t *idxarr
, const unsigned char *rulearr
)
407 uint32_t nrules
= l_data
->nrules
;
408 unsigned char *rulesets
= l_data
->rulesets
;
409 USTRING_TYPE
*weights
= l_data
->weights
;
415 /* Now the passes over the weights. */
416 for (pass
= 0; pass
< nrules
; ++pass
)
418 size_t backw_stop
= ~0ul;
419 int rule
= rulesets
[rulearr
[0] * nrules
+ pass
];
420 /* We assume that if a rule has defined `position' in one section
421 this is true for all of them. */
422 int position
= rule
& sort_position
;
424 last_needed
= needed
;
427 for (idxcnt
= 0; idxcnt
< idxmax
; ++idxcnt
)
429 if ((rule
& sort_forward
) != 0)
433 if (backw_stop
!= ~0ul)
435 /* Handle the pushed elements now. */
438 for (backw
= idxcnt
; backw
> backw_stop
; )
441 len
= weights
[idxarr
[backw
]++];
443 if (needed
+ len
< n
)
445 dest
[needed
++] = weights
[idxarr
[backw
]++];
448 /* No more characters fit into the buffer. */
450 idxarr
[backw
] += len
;
457 /* Now handle the forward element. */
458 len
= weights
[idxarr
[idxcnt
]++];
459 if (needed
+ len
< n
)
461 dest
[needed
++] = weights
[idxarr
[idxcnt
]++];
464 /* No more characters fit into the buffer. */
466 idxarr
[idxcnt
] += len
;
471 /* Remember where the backwards series started. */
472 if (backw_stop
== ~0ul)
476 rule
= rulesets
[rulearr
[idxcnt
+ 1] * nrules
+ pass
];
480 if (backw_stop
!= ~0ul)
482 /* Handle the pushed elements now. */
486 while (backw
> backw_stop
)
488 size_t len
= weights
[idxarr
[--backw
]++];
490 if (needed
+ len
< n
)
492 dest
[needed
++] = weights
[idxarr
[backw
]++];
495 /* No more characters fit into the buffer. */
497 idxarr
[backw
] += len
;
505 #ifndef WIDE_CHAR_VERSION
511 for (idxcnt
= 0; idxcnt
< idxmax
; ++idxcnt
)
513 if ((rule
& sort_forward
) != 0)
517 if (backw_stop
!= ~0ul)
519 /* Handle the pushed elements now. */
522 for (backw
= idxcnt
; backw
> backw_stop
; )
525 len
= weights
[idxarr
[backw
]++];
528 #ifdef WIDE_CHAR_VERSION
529 if (needed
+ 1 + len
< n
)
532 for (i
= 0; i
< len
; ++i
)
533 dest
[needed
+ 1 + i
] =
534 weights
[idxarr
[backw
] + i
];
538 buflen
= utf8_encode (buf
, val
);
539 if (needed
+ buflen
+ len
< n
)
541 for (i
= 0; i
< buflen
; ++i
)
542 dest
[needed
+ i
] = buf
[i
];
543 for (i
= 0; i
< len
; ++i
)
544 dest
[needed
+ buflen
+ i
] =
545 weights
[idxarr
[backw
] + i
];
547 needed
+= buflen
+ len
;
549 idxarr
[backw
] += len
;
559 /* Now handle the forward element. */
560 len
= weights
[idxarr
[idxcnt
]++];
563 #ifdef WIDE_CHAR_VERSION
564 if (needed
+ 1+ len
< n
)
567 for (i
= 0; i
< len
; ++i
)
568 dest
[needed
+ 1 + i
] =
569 weights
[idxarr
[idxcnt
] + i
];
573 buflen
= utf8_encode (buf
, val
);
574 if (needed
+ buflen
+ len
< n
)
576 for (i
= 0; i
< buflen
; ++i
)
577 dest
[needed
+ i
] = buf
[i
];
578 for (i
= 0; i
< len
; ++i
)
579 dest
[needed
+ buflen
+ i
] =
580 weights
[idxarr
[idxcnt
] + i
];
582 needed
+= buflen
+ len
;
584 idxarr
[idxcnt
] += len
;
588 /* Note that we don't have to increment `idxarr[idxcnt]'
589 since the length is zero. */
594 /* Remember where the backwards series started. */
595 if (backw_stop
== ~0ul)
599 rule
= rulesets
[rulearr
[idxcnt
+ 1] * nrules
+ pass
];
602 if (backw_stop
!= ~0ul)
604 /* Handle the pushed elements now. */
608 while (backw
> backw_stop
)
610 size_t len
= weights
[idxarr
[--backw
]++];
613 #ifdef WIDE_CHAR_VERSION
614 if (needed
+ 1 + len
< n
)
617 for (i
= 0; i
< len
; ++i
)
618 dest
[needed
+ 1 + i
] =
619 weights
[idxarr
[backw
] + i
];
623 buflen
= utf8_encode (buf
, val
);
624 if (needed
+ buflen
+ len
< n
)
626 for (i
= 0; i
< buflen
; ++i
)
627 dest
[needed
+ i
] = buf
[i
];
628 for (i
= 0; i
< len
; ++i
)
629 dest
[needed
+ buflen
+ i
] =
630 weights
[idxarr
[backw
] + i
];
632 needed
+= buflen
+ len
;
634 idxarr
[backw
] += len
;
643 /* Finally store the byte to separate the passes or terminate
646 dest
[needed
] = pass
+ 1 < nrules
? L('\1') : L('\0');
650 /* This is a little optimization: many collation specifications have
651 a `position' rule at the end and if no non-ignored character
652 is found the last \1 byte is immediately followed by a \0 byte
653 signalling this. We can avoid the \1 byte(s). */
654 if (needed
> 2 && needed
== last_needed
+ 1)
656 /* Remove the \1 byte. */
658 dest
[needed
- 1] = L('\0');
661 /* Return the number of bytes/words we need, but don't count the NUL
662 byte/word at the end. */
667 STRXFRM (STRING_TYPE
*dest
, const STRING_TYPE
*src
, size_t n
, locale_t l
)
669 locale_data_t l_data
;
670 struct __locale_data
*current
= l
->__locales
[LC_COLLATE
];
671 l_data
.nrules
= current
->values
[_NL_ITEM_INDEX (_NL_COLLATE_NRULES
)].word
;
673 /* Handle byte comparison case. */
674 if (l_data
.nrules
== 0)
676 size_t srclen
= STRLEN (src
);
679 STPNCPY (dest
, src
, MIN (srclen
+ 1, n
));
684 /* Handle an empty string, code hereafter relies on strlen (src) > 0. */
692 /* Get the locale data. */
693 l_data
.rulesets
= (unsigned char *)
694 current
->values
[_NL_ITEM_INDEX (_NL_COLLATE_RULESETS
)].string
;
695 l_data
.table
= (int32_t *)
696 current
->values
[_NL_ITEM_INDEX (CONCAT(_NL_COLLATE_TABLE
,SUFFIX
))].string
;
697 l_data
.weights
= (USTRING_TYPE
*)
698 current
->values
[_NL_ITEM_INDEX (CONCAT(_NL_COLLATE_WEIGHT
,SUFFIX
))].string
;
699 l_data
.extra
= (USTRING_TYPE
*)
700 current
->values
[_NL_ITEM_INDEX (CONCAT(_NL_COLLATE_EXTRA
,SUFFIX
))].string
;
701 l_data
.indirect
= (int32_t *)
702 current
->values
[_NL_ITEM_INDEX (CONCAT(_NL_COLLATE_INDIRECT
,SUFFIX
))].string
;
704 assert (((uintptr_t) l_data
.table
) % __alignof__ (l_data
.table
[0]) == 0);
705 assert (((uintptr_t) l_data
.weights
) % __alignof__ (l_data
.weights
[0]) == 0);
706 assert (((uintptr_t) l_data
.extra
) % __alignof__ (l_data
.extra
[0]) == 0);
707 assert (((uintptr_t) l_data
.indirect
) % __alignof__ (l_data
.indirect
[0]) == 0);
709 /* We need the elements of the string as unsigned values since they
710 are used as indices. */
711 const USTRING_TYPE
*usrc
= (const USTRING_TYPE
*) src
;
713 /* Allocate cache for small strings on the stack and fill it with weight and
714 rule indices. If the cache size is not sufficient, continue with the
715 uncached xfrm version. */
717 const USTRING_TYPE
*cur
= usrc
;
718 int32_t *idxarr
= alloca (SMALL_STR_SIZE
* sizeof (int32_t));
719 unsigned char *rulearr
= alloca (SMALL_STR_SIZE
+ 1);
723 int32_t tmp
= findidx (l_data
.table
, l_data
.indirect
, l_data
.extra
, &cur
,
725 rulearr
[idxmax
] = tmp
>> 24;
726 idxarr
[idxmax
] = tmp
& 0xffffff;
730 while (*cur
!= L('\0') && idxmax
< SMALL_STR_SIZE
);
732 /* This element is only read, the value never used but to determine
733 another value which then is ignored. */
734 rulearr
[idxmax
] = '\0';
736 /* Do the transformation. */
738 return do_xfrm_cached (dest
, n
, &l_data
, idxmax
, idxarr
, rulearr
);
740 return do_xfrm (usrc
, dest
, n
, &l_data
);
742 libc_hidden_def (STRXFRM
)
744 #ifndef WIDE_CHAR_VERSION
745 weak_alias (__strxfrm_l
, strxfrm_l
)