public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 1/1] string: Add stpecpy(3)
@ 2022-12-23 14:59 Wilco Dijkstra
  2022-12-23 17:03 ` Alejandro Colomar
  0 siblings, 1 reply; 26+ messages in thread
From: Wilco Dijkstra @ 2022-12-23 14:59 UTC (permalink / raw)
  To: Alejandro Colomar (man-pages); +Cc: 'GNU C Library'

Hi Alex,

>> Given strlcpy and strlcat are in POSIX next and therefore bar
>> some extraordinary event will be in glibc, I think we should
>> probably wait until those two land, then see if there's still
>> an appetite for stpecpy in glibc.
>
> I disagree for the following reasons.
>
> strlcpy(3)/strlcat(3) are designed to be _slow_, in exchange for added 
> simplicity and safety.  That's what Theo told me about them.  They didn't care 
> about performance.  The two performance issues are:

We'd need actual benchmarks to confirm there is a detectable performance
difference in typical scenarios. So calling strlcpy slow is premature. Looking
at the proposed stpecpy, it seems it has a lot more branches and special cases
compared to a typical strlcpy, and that adds extra overhead. Using memccpy
is risky too since that is often not optimized.

> -  Traverse the entire input string, to make sure it's a string.  stpecpy(3) 
> instead only reads what's necessary for the copy; it stops reading after truncation.

Almost all strings are short and few cases need truncation, so I don't see the issue.
People concerned about performance wouldn't use the standard string functions
anyway.

> -  strlcat(3) finds the terminating null byte; that's something you already know 
> where it is, with functions that return a useful pointer (mempcpy(3), stpcpy(3), 
> and stpecpy(3)).

If you know the end of the destination string then don't use concatenation. Easy!

In fact compilers could inline the 'dest += strlen (dest)' part of strcat and call
strcpy instead. This allows optimization of the strlen in case you know the size
of the destination string. This is true for strlcpy too, a compiler could just inline
it and optimize the strlen (src).

> The reason that triggered me wanting to add this function is seeing strncpy(3) 
> used for a patch to some glibc internals themselves.  Using strlcpy(3)/cat(3) in 
> glibc internals would be bad for performance; I would hope that glibc uses the 
> optimal internals, even if it also provides slow functions for users.

Most internal uses are unlikely to be performance critical, and correctness is kind
of important for libraries.

IMHO inventing many slightly different non-standard string functions is what
causes performance and correctness issues. People disagree about the semantics
(strlcpy has been argued over for a decade or so). Even if a library supports them,
you never know which implementations are actually well optimized (obviously
this varies between ISA and different libc's). So which non-standard string function
is safe to use across all targets and libraries?

In contrast we can be pretty sure that the standard strlen, memcpy etc are both
correct and efficient on all targets/libc's.

Cheers,
Wilco

^ permalink raw reply	[flat|nested] 26+ messages in thread
* Re: [PATCH 1/1] string: Add stpecpy(3)
@ 2022-12-23 23:24 Wilco Dijkstra
  2022-12-24  0:05 ` Alejandro Colomar
  0 siblings, 1 reply; 26+ messages in thread
From: Wilco Dijkstra @ 2022-12-23 23:24 UTC (permalink / raw)
  To: Alejandro Colomar; +Cc: 'GNU C Library'

Hi Alex,

> For that, we'd first need to discuss what is a typical scenario.

Like copying/concatenating strings that fit within the buffer.

> And also, it depends a lot on what the compiler can optimize.  If I call 
> strlcat(3) in a loop, I know that stpecpy(3) is going to be orders of magnitude 
> faster.

If you're trying to say that the 'strcat' variant is bad then yes absolutely -
it's better to inline in the compiler or avoid the 'strcat' versions altogether
(that's also why I would strongly suggest never to add more 'cat' variants).
But that doesn't say anything about whether stpecpy is better than strlcpy.

> If I call strlcpy(3) in a loop, doing what an ideal compiler might do, that 
> might be something to benchmark, but we'd also need to discuss what is a good 
> input for the benchmark.

The typical case would be copying or concatenating smallish strings to a buffer.

> In the OpenBSD definition of strlcpy(), I count 4 branches, and one of them is 
> inside a while loop.  So, I'd find it very surprising if strlcpy(3) outperformed 
> stpecpy(3).

If that really is the OpenBSD implementation then this proves my point that
non-standard string functions are often totally unoptimized.

A basic implementation of strlcpy would use strlen and memcpy so it is fast
on every system without requiring any optimization:

size_t
strlcpy (char *dst, const char *src, size_t size)
{
  size_t len = strlen (src);

  if (size == 0)
    return len;
  size = len >= size ? size - 1 : len;
  dst[size] = 0;
  memcpy (dst, src, size);
  return len;
}

> Well, with the current memccpy(3) I already suspect it's going to be faster than 
> strlcpy(3).  If you optimize it, it would increase the chances that it's faster :)

I don't see why it would be any faster given memccpy might also not be
optimized.

> I find it _way_ more readable than the strlcpy(3)/cat(3) code.  Oh, and did I 
> say it has less branches? :)

I'm not so sure about that - you've got 3 call/returns plus at least 4 branches
for each stpecpy (besides whatever memcpy/memchr do). strlcpy has 2 calls/
returns plus one branch. So needing an extra branch in case you need to do
something special for the buffer full case doesn't seem like a major problem.

>> In contrast we can be pretty sure that the standard strlen, memcpy etc are both
>> correct and efficient on all targets/libc's.
>
> Sure, but memcpy(3) is not usable in code that needs to truncate.  We need to 
> compare against stpncpy(3) (ughhh) and strlcpy(3).

The idea is that if we add new string functions, their implementation should use
other string functions that are known to be well optimized for most targets.

Cheers,
Wilco

^ permalink raw reply	[flat|nested] 26+ messages in thread
* Re: [PATCH 1/1] string: Add stpecpy(3)
@ 2022-12-23 18:35 Wilco Dijkstra
  2022-12-23 22:40 ` Alejandro Colomar
  0 siblings, 1 reply; 26+ messages in thread
From: Wilco Dijkstra @ 2022-12-23 18:35 UTC (permalink / raw)
  To: Alejandro Colomar; +Cc: 'GNU C Library'

Hi Alex,

>      if (dst == end)
>          return end;
>      if (stp_unlikely(dst == NULL))  // Allow chaining with stpeprintf().
>          return NULL;

> Oh, and the two branches above can be optimized into a branch that returns dst. 

How? There will be 2 branches since you're doing 2 checks here...

Cheers,
Wilco

^ permalink raw reply	[flat|nested] 26+ messages in thread
* [PATCH 0/1] string: Add stpecpy(3)
@ 2022-12-22 21:42 Alejandro Colomar
  2022-12-22 21:42 ` [PATCH 1/1] " Alejandro Colomar
  0 siblings, 1 reply; 26+ messages in thread
From: Alejandro Colomar @ 2022-12-22 21:42 UTC (permalink / raw)
  To: libc-alpha; +Cc: Alejandro Colomar

Hi!

I recently rewrite the Linux man-pages for string-copying functions, and
just a moment ago, I released the new version.  There are some important
gaps in the string-copying library, and they should be addressed,
however it is done, but ignoring it won't solve the problem.

I know this has been suggested in the past (I did once), and has never
progressed, but I'll try to justify it as much as I can.

The gaps are:

-  No function for copying strings with truncation.  (strlcpy(3) or strscpy(9))
-  No function for catenating strings with truncation.  (strlcat(3))
-  No function for chain-copying strings with truncation.  (stpecpy(3))

   This is similar to strcpy(3)/strcat(3)/stpcpy(3), where stpcpy(3) is
   faster and more versatile than the other two, but it's also slightly
   more complex to use (only slightly).

   We wouldn't need to add the 3, but at least stpecpy(3) or both
   strlcpy(3) and strlcat(3).

   Since stpecpy(3) is significantly faster than the other two, I
   suggest at least adding stpecpy(3).  Also, strlcpy(3)/cat(3) can be
   more easily implemented in terms of stpecpy(3).

There are a few other gaps, but they are much less important, since
there are relatively good workarounds.  I don't want to overload the
discussion either, so I prefer first adding the most necessary function,
and only after that deciding if we want to support other string-copying
functions.

I had added this function this week to a new string library that I was
writing, so I already had a manual page written for it.  I'll copy it
below, to document all the details of the API.o

Cheers,

Alex


---

Alejandro Colomar (1):
  string: Add stpecpy(3)

 string/Makefile  |  1 +
 string/stpecpy.c | 39 +++++++++++++++++++++++++++++++++++++++
 string/string.h  |  7 +++++++
 3 files changed, 47 insertions(+)
 create mode 100644 string/stpecpy.c


stpecpy(3)                 Library Functions Manual                 stpecpy(3)

NAME
       stpecpy, stpecpyx - copy a string with truncation

LIBRARY
       Stp string library (libstp, pkgconf ‐‐cflags ‐‐libs libstp)

SYNOPSIS
       #include <stp/stpe/stpecpy.h>

       char *_Nullable stpecpy(char *_Nullable dst, char end[0],
                               const char *restrict src);
       char *_Nullable stpecpyx(char *_Nullable dst, char end[0],
                               const char *restrict src);

DESCRIPTION
       These functions copy the string pointer to by src, into a string at the
       buffer  pointer  to  by  dst.   If the destination buffer, limited by a
       pointer to its end —one after its last element—, isn’t large enough  to
       hold the copy, the resulting string is truncated.

       stpecpyx(3)  forces a SIGSEGV if the input is not a string, by travers‐
       ing it entirely.

       These  functions  can  be  chained  with  calls  to  stpeprintf(3)  and
       vstpeprintf(3).

       An implementation of these functions might be

           /* This code is in the public domain. */

           char *
           stpecpy(char *dst, char end[0], const char *restrict src)
           {
               char *p;

               if (dst == end || dst == NULL)
                   return dst;

               p = memccpy(dst, src, '\0', end - dst);
               if (p != NULL)
                   return p - 1;

               /* truncation detected */
               end[-1] = '\0';
               return end;
           }

           char *
           stpecpyx(char *dst, char end[0], const char *restrict src)
           {
               if (src[strlen(src)] != '\0')
                   raise(SIGSEGV);

               return stpecpy(dst, end, src);
           }

RETURN VALUE
       NULL   If dst was NULL.

       end
              •  If this call truncated.
              •  If  dst  was equal to end (a previous call to these functions
                 truncated).

       dst + strlen(dst)
              On success, these functions return a pointer to the  terminating
              null byte.

ATTRIBUTES
       For  an  explanation  of  the  terms  used in this section, see attrib‐
       utes(7).
       ┌────────────────────────────────────────────┬───────────────┬─────────┐
       │Interface                                   │ Attribute     │ Value   │
       ├────────────────────────────────────────────┼───────────────┼─────────┤
       │stpecpy(3), stpecpyx(3)                     │ Thread safety │ MT‐Safe │
       └────────────────────────────────────────────┴───────────────┴─────────┘

STANDARDS
       None.

EXAMPLES
       $ cc ./stpecpy.c $(pkgconf --cflags --libs libbsd-overlay libstp)
       $ ./a.out
       [len = 12]: Hello world!
       $

       // stpecpy.c
       #include <err.h>
       #include <stdio.h>
       #include <stdlib.h>

       #include <stp/stpecpy.h>
       #include <stp/stpeprintf.h>

       int
       main(void)
       {
           char    *p, *end;
           char    buf[BUFSIZ];
           size_t  len;

           end = buf + BUFSIZ;
           p = buf;
           p = stpecpy(p, end, "Hello, ");
           p = stpeprintf(p, end, "%d worlds", 22);
           p = stpecpy(p, end, "!");
           if (p == NULL)
               err(EXIT_FAILURE, "stpeprintf()");
           if (p == end) {
               p--;
               warnx("Truncated");
           }
           len = p - buf;
           printf("[len = %zu]: ", len);
           puts(buf);

           exit(EXIT_SUCCESS);
       }

SEE ALSO
       stpeprintf(3), string_copying(7)

libstp (unreleased)                 (date)                          stpecpy(3)

-- 
2.39.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2022-12-31 15:15 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-23 14:59 [PATCH 1/1] string: Add stpecpy(3) Wilco Dijkstra
2022-12-23 17:03 ` Alejandro Colomar
2022-12-23 17:27   ` Alejandro Colomar
  -- strict thread matches above, loose matches on Subject: below --
2022-12-23 23:24 Wilco Dijkstra
2022-12-24  0:05 ` Alejandro Colomar
2022-12-24  0:26   ` Alejandro Colomar
2022-12-25  1:52     ` Noah Goldstein
2022-12-25 14:37       ` Alejandro Colomar
2022-12-25 22:31         ` Noah Goldstein
2022-12-26  0:26           ` Alejandro Colomar
2022-12-26  0:32             ` Noah Goldstein
2022-12-26  0:37               ` Alejandro Colomar
2022-12-26  2:43                 ` Noah Goldstein
2022-12-26 22:25                   ` Alejandro Colomar
2022-12-26 23:24                     ` Alejandro Colomar
2022-12-26 23:52                       ` Alejandro Colomar
2022-12-27  0:12                         ` Alejandro Colomar
2022-12-23 18:35 Wilco Dijkstra
2022-12-23 22:40 ` Alejandro Colomar
2022-12-22 21:42 [PATCH 0/1] " Alejandro Colomar
2022-12-22 21:42 ` [PATCH 1/1] " Alejandro Colomar
2022-12-23  7:02   ` Sam James
2022-12-23 12:26     ` Alejandro Colomar
2022-12-23 12:29       ` Alejandro Colomar
2022-12-23 17:21       ` Alejandro Colomar
2022-12-31 15:13       ` Sam James
2022-12-31 15:15         ` Alejandro Colomar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).