Re: [PATCH] wip: change ISSPACE to use ascii whitespace check.

Alejandro Colomar via Mutt-dev <[email protected]>
Newsgroups gmane.mail.mutt.devel
Message-ID <aZcQBd0v7RaKXv3w@devuan>
On 2026-02-19T12:52:00+0100, Alejandro Colomar wrote:
> Hi Ian,
> 
> On 2026-02-19T10:30:09+0000, Ian Collier via Mutt-dev wrote:
> > On Thu, Feb 19, 2026 at 01:46:34AM +0100, Alejandro Colomar via Mutt-dev wrote:
> > > On 2026-02-18T23:42:28+0000, Ian Collier via Mutt-dev wrote:
> > > > I do not understand why strchrnul and strcmp are even being invoked here.
> > 
> > > Because they are necessary.  (Well, strcmp(3) is not, as you could do
> > > manual byte operations, but it's simple, more readable, and optimized
> > > out.)  strchrnul(3) is necessary.
> > 
> > > 	strchr(s, '\0') != NULL
> > 
> > > would evaluate to true, which is a misbehavior: it would treat '\0' as
> > > white space, while it is not. 
> > 
> > The nul character is a bit of an edge case when considering strxxx functions,
> > of course.  But that's easily fixed:
> > 
> >  c && strchr(s, c) != NULL
> > 
> > No doubt you'll now be telling me that you don't want to evaluate c twice
> > in the context of a macro.  Well then, use an inline function. :-)
> 
> An inline function with that would be an alternative.  A macro is more
> compact, though, and can be implemented as a one-liner.  When
> implementing several of these, it can help readability of the entire set
> of APIs if they fit in a compact space and they are very similar:
> 
> 	// isascii_c - is [:ascii:] C-locale
> 	#define isascii_c(c)   (!!strchr(CTYPE_ASCII_C, c))
> 	#define iscntrl_c(c)   (!!strchr(CTYPE_CNTRL_C, c))
> 	#define islower_c(c)   (!streq(strchrnul(CTYPE_LOWER_C, c), ""))
> 	#define isupper_c(c)   (!streq(strchrnul(CTYPE_UPPER_C, c), ""))
> 	#define isdigit_c(c)   (!streq(strchrnul(CTYPE_DIGIT_C, c), ""))
> 	#define ispunct_c(c)   (!streq(strchrnul(CTYPE_PUNCT_C, c), ""))
> 	#define isspace_c(c)   (!streq(strchrnul(CTYPE_SPACE_C, c), ""))
> 	#define isalpha_c(c)   (!streq(strchrnul(CTYPE_ALPHA_C, c), ""))
> 	#define isalnum_c(c)   (!streq(strchrnul(CTYPE_ALNUM_C, c), ""))
> 	#define isgraph_c(c)   (!streq(strchrnul(CTYPE_GRAPH_C, c), ""))
> 	#define isprint_c(c)   (!streq(strchrnul(CTYPE_PRINT_C, c), ""))
> 	#define isxdigit_c(c)  (!streq(strchrnul(CTYPE_XDIGIT_C, c), ""))
> 
> Here's a comparison of the assembly they produce:
> 
> 	alx@devuan:~/tmp$ diff -u isspace?.c
> 	--- isspace1.c	2026-02-19 12:43:01.016632233 +0100
> 	+++ isspace2.c	2026-02-19 12:39:40.987744755 +0100
> 	@@ -1,8 +1,7 @@
> 	 #include <string.h>
> 	 #define CTYPE_SPACE_C   " \t\n\v\f\r"
> 	-#define streq(a,b)  (strcmp(a,b) == 0)
> 	 bool
> 	 isspace_c(int c)
> 	 {
> 	-	return !streq(strchrnul(CTYPE_SPACE_C, c), "");
> 	+	return c && strchr(CTYPE_SPACE_C, c) != NULL;
> 	 }
> 	alx@devuan:~/tmp$ gcc -S -O2 isspace?.c
> 	alx@devuan:~/tmp$ diff -u isspace?.s
> 	--- isspace1.s	2026-02-19 12:44:24.264776257 +0100
> 	+++ isspace2.s	2026-02-19 12:44:24.276776354 +0100
> 	@@ -1,4 +1,4 @@
> 	-	.file	"isspace1.c"
> 	+	.file	"isspace2.c"
> 		.text
> 		.section	.rodata.str1.1,"aMS",@progbits,1
> 	 .LC0:
> 	@@ -10,16 +10,23 @@
> 	 isspace_c:
> 	 .LFB0:
> 		.cfi_startproc
> 	+	testl	%edi, %edi
> 	+	je	.L5
> 		subq	$8, %rsp
> 		.cfi_def_cfa_offset 16
> 		movl	%edi, %esi
> 		leaq	.LC0(%rip), %rdi
> 	-	call	strchrnul@PLT
> 	-	cmpb	$0, (%rax)
> 	+	call	strchr@PLT
> 	+	testq	%rax, %rax
> 		setne	%al
> 		addq	$8, %rsp
> 		.cfi_def_cfa_offset 8
> 		ret
> 	+	.p2align 4,,10
> 	+	.p2align 3
> 	+.L5:
> 	+	xorl	%eax, %eax
> 	+	ret
> 		.cfi_endproc
> 	 .LFE0:
> 		.size	isspace_c, .-isspace_c
> 
> The strchrnul(3) version seems to have significantly less instructions.
> 
> And strchrnul(3) is internally simpler than strchr(3).  In musl, for
> example, strchrnul(3) is used to implement strchr(3).

Here's the comparison between isspace_c() using non-system strchrnul()
vs the brancy implementation with strchr(3):

	alx@devuan:~/tmp$ diff -u isspace[23].c
	--- isspace2.c	2026-02-19 12:39:40.987744755 +0100
	+++ isspace3.c	2026-02-19 14:26:39.575137468 +0100
	@@ -1,7 +1,14 @@
	 #include <string.h>
	 #define CTYPE_SPACE_C   " \t\n\v\f\r"
	+#define streq(a,b)  (strcmp(a,b) == 0)
	+#define strnul(s)  strchr(s, '\0')
	+static inline char *
	+my_strchrnul(const char *s, int c)
	+{
	+	return strchr(s, c) ?: strnul(s);
	+}
	 bool
	 isspace_c(int c)
	 {
	-	return c && strchr(CTYPE_SPACE_C, c) != NULL;
	+	return !streq(my_strchrnul(CTYPE_SPACE_C, c), "");
	 }
	alx@devuan:~/tmp$ gcc -S -O2 isspace[23].c
	alx@devuan:~/tmp$ diff -u isspace[23].s
	--- isspace2.s	2026-02-19 14:27:20.193097036 +0100
	+++ isspace3.s	2026-02-19 14:27:20.205097129 +0100
	@@ -1,4 +1,4 @@
	-	.file	"isspace2.c"
	+	.file	"isspace3.c"
		.text
		.section	.rodata.str1.1,"aMS",@progbits,1
	 .LC0:
	@@ -8,27 +8,25 @@
		.globl	isspace_c
		.type	isspace_c, @function
	 isspace_c:
	-.LFB0:
	+.LFB1:
		.cfi_startproc
	-	testl	%edi, %edi
	-	je	.L5
	+	movl	%edi, %esi
		subq	$8, %rsp
		.cfi_def_cfa_offset 16
	-	movl	%edi, %esi
		leaq	.LC0(%rip), %rdi
		call	strchr@PLT
	-	testq	%rax, %rax
	+	movq	%rax, %rdx
	+	xorl	%eax, %eax
	+	testq	%rdx, %rdx
	+	je	.L1
	+	cmpb	$0, (%rdx)
		setne	%al
	+.L1:
		addq	$8, %rsp
		.cfi_def_cfa_offset 8
		ret
	-	.p2align 4,,10
	-	.p2align 3
	-.L5:
	-	xorl	%eax, %eax
	-	ret
		.cfi_endproc
	-.LFE0:
	+.LFE1:
		.size	isspace_c, .-isspace_c
		.ident	"GCC: (Debian 15.2.0-13) 15.2.0"
		.section	.note.GNU-stack,"",@progbits

I expect the strchr(3) version would be faster, since the branch is
rarely used, and the main body is smaller.  However, I wouldn't worry
about performance in systems that don't implement strchrnul(3); those
are rare, and the difference would be small-ish.  Let the systems
implement strchrnul(3) if they care about performance.


Cheers,
Alex

-- 
<https://www.alejandro-colomar.es>
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmmXEOwACgkQ64mZXMKQ
wqlw9Q//ZhmcUBqJAiPwdtcNID6vjwsvRBav4/XS4zMzJ4hRTQlFrWs4Gr3qA/bl
JqirbhG9+Dz3tai9C6D5G3TMNGd/UpY2Hzliapmii/o0lsZrRMU3ZgM9MAUZrO2q
A1pE8jJX6gy/IAM2bdVYfCxPdRtxtVpvW/PsOh8SboGxasSyEbQpft3++OSAcfGR
3k/WhLILm0i532VlWDKlLG/9iESpJJGXfPfCmCnEGYwVHLoLtcZUeZlRE7Qp0JAg
4zF/cnS88wJYUwhHQTWYCxF2Dnfi2SB4A2ADjyz+Puwubd6DlSeABnoIm5fE0Ur9
6wniptKONMu1t25eRMj23/DNo82j2eymHfXWu5/dKqeqt+z9u90EftjX5yrnea5f
RMwQvGM1L1uX+7D80gZhfYaSLYtIAaAikLIQPaSQ1JlWCsAjdllOHG+uADmvj6eg
HjoFKwhUIgXE5mX2TKukmHV8WUoyqoF2BHw/CRoGk0t3lTrzGTjedCBYvVH9GqjO
c8xWgl4AIw3lnamCKJ8BUosVd8YuGqJuWugei5z4iSHte0Jt4boGHly81yvZJ7r2
AaOLTU37fi9WBqCuyQDU0SyWvBjZqhBdP9ryGzBQCXKPO5U3ZmJUtRZHXJ2rl/MN
TMiGUgk2Ts0ftagAOJ5F9HvcpM2scOVtUIxhVew4QGuFgBDfpHQ=
=/tdw
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.