diff mbox series

tools/nolibc: fix up size inflate regression

Message ID 20230806131921.52453-1-falcon@tinylab.org (mailing list archive)
State New
Headers show
Series tools/nolibc: fix up size inflate regression | expand

Commit Message

Zhangjin Wu Aug. 6, 2023, 1:19 p.m. UTC
As reported and suggested by Willy, the inline __sysret() helper
introduces three types of conversions and increases the size:

(1) the "unsigned long" argument to __sysret() forces a sign extension
from all sys_* functions that used to return 'int'

(2) the comparison with the error range now has to be performed on a
'unsigned long' instead of an 'int'

(3) the return value from __sysret() is a 'long' (note, a signed long)
which then has to be turned back to an 'int' before being returned by the
caller to satisfy the caller's prototype.

To fix up this, firstly, let's use macro instead of inline function to
preserves the input type and avoids these useless conversions (1), (3).

Secondly, comparison to -MAX_ERRNO inflicts on all integer returns where
we could previously keep a simple sign comparison, let's use a new
is_signed_type() macro from include/linux/compiler.h to limit the
comparision to -MAX_ERRNO (2) only on demand and preserves a simple sign
comparision for most of the cases as before.

Thirdly, fix up the following warning by an explicit conversion:

    sysroot/powerpc/include/sys.h: In function 'sbrk':
    sysroot/powerpc/include/sys.h:104:16: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
      104 |         return (void *)__sysret(-ENOMEM);

Here reports the size testing result with nolibc-test:

before:

    // ppc64le
    $ size nolibc-test
       text	   data	    bss	    dec	    hex	filename
      27916	      8	     80	  28004	   6d64	nolibc-test

    // mips
    $ size nolibc-test
       text	   data	    bss	    dec	    hex	filename
      23276	     64	     64	  23404	   5b6c	nolibc-test

after:

    // ppc64le
    $ size nolibc-test
       text	   data	    bss	    dec	    hex	filename
      27736	      8	     80	  27824	   6cb0	nolibc-test

    // mips
    $ size nolibc-test
       text	   data	    bss	    dec	    hex	filename
      23036	     64	     64	  23164	   5a7c	nolibc-test

Suggested-by: Willy Tarreau <w@1wt.eu>
Link: https://lore.kernel.org/lkml/20230806095846.GB10627@1wt.eu/#R
Signed-off-by: Zhangjin Wu <falcon@tinylab.org>
---
 tools/include/nolibc/compiler.h |  9 +++++++++
 tools/include/nolibc/sys.h      | 27 +++++++++++++++++----------
 2 files changed, 26 insertions(+), 10 deletions(-)

Comments

Willy Tarreau Aug. 6, 2023, 1:43 p.m. UTC | #1
On Sun, Aug 06, 2023 at 09:19:21PM +0800, Zhangjin Wu wrote:
> As reported and suggested by Willy, the inline __sysret() helper
> introduces three types of conversions and increases the size:

Thanks Zhangjin. A few things:

> --- a/tools/include/nolibc/compiler.h
> +++ b/tools/include/nolibc/compiler.h
> @@ -22,4 +22,13 @@
>  #  define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector")))
>  #endif /* defined(__has_attribute) */
>  
> +/*
> + * from include/linux/compiler.h
> + *
> + * Whether 'type' is a signed type or an unsigned type. Supports scalar types,
> + * bool and also pointer types.
> + */
> +#define is_signed_type(type)   (((type)(-1)) < (type)1)
> +#define is_unsigned_type(type) (!is_signed_type(type))

These names may conflict with application's local definitions. And since
there's a single call place we should probably just inline it instead.

> +#define __sysret(arg)                                                           \
> +({                                                                              \
> +	__typeof__(arg) __sysret_arg = (arg);                                   \
> +	if (is_signed_type(__typeof__(arg))) {                                  \
> +		if (__sysret_arg < 0) {                                         \
> +			SET_ERRNO(-(int)__sysret_arg);                          \
> +			__sysret_arg = -1L;                                     \
> +		}                                                               \
> +	} else {                                                                \
> +		if ((unsigned long)__sysret_arg >= (unsigned long)-MAX_ERRNO) { \
> +			SET_ERRNO(-(int)__sysret_arg);                          \
> +			__sysret_arg = -1L;                                     \
> +		}                                                               \
> +	}                                                                       \
> +	__sysret_arg;                                                           \
> +})

I also found during my tests that this one can return a build error if
the arg has "const" in its type, due to the error assignment. We need
to think about reworking it as a ternary evaluation, it will be more
reliable even if less readable. But let's not change this now, I'm on
the changelog already.

>  /* Functions in this file only describe syscalls. They're declared static so
>   * that the compiler usually decides to inline them while still being allowed
> @@ -94,7 +101,7 @@ void *sbrk(intptr_t inc)
>  	if (ret && sys_brk(ret + inc) == ret + inc)
>  		return ret + inc;
>  
> -	return (void *)__sysret(-ENOMEM);
> +	return (void *)__sysret((unsigned long)-ENOMEM);

I noticed that one as well during my tests, but if we're purist, we're
supposed to use (void*) for the cast and not (unsigned long), and it
should allow to get rid of the outer cast.

Regards,
Willy
Zhangjin Wu Aug. 6, 2023, 4:36 p.m. UTC | #2
Hi, Willy

> On Sun, Aug 06, 2023 at 09:19:21PM +0800, Zhangjin Wu wrote:
> > As reported and suggested by Willy, the inline __sysret() helper
> > introduces three types of conversions and increases the size:
> 
> Thanks Zhangjin. A few things:
> 
> > --- a/tools/include/nolibc/compiler.h
> > +++ b/tools/include/nolibc/compiler.h
> > @@ -22,4 +22,13 @@
> >  #  define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector")))
> >  #endif /* defined(__has_attribute) */
> >  
> > +/*
> > + * from include/linux/compiler.h
> > + *
> > + * Whether 'type' is a signed type or an unsigned type. Supports scalar types,
> > + * bool and also pointer types.
> > + */
> > +#define is_signed_type(type)   (((type)(-1)) < (type)1)
> > +#define is_unsigned_type(type) (!is_signed_type(type))
> 
> These names may conflict with application's local definitions. And since
> there's a single call place we should probably just inline it instead.
>

Let's remove them and define a new one with __ prefix in sys.h, it is
not readable if we simply 'inline' the comparison in the already complex
macro ;-)

> > +#define __sysret(arg)                                                           \
> > +({                                                                              \
> > +	__typeof__(arg) __sysret_arg = (arg);                                   \
> > +	if (is_signed_type(__typeof__(arg))) {                                  \
> > +		if (__sysret_arg < 0) {                                         \
> > +			SET_ERRNO(-(int)__sysret_arg);                          \
> > +			__sysret_arg = -1L;                                     \
> > +		}                                                               \
> > +	} else {                                                                \
> > +		if ((unsigned long)__sysret_arg >= (unsigned long)-MAX_ERRNO) { \
> > +			SET_ERRNO(-(int)__sysret_arg);                          \
> > +			__sysret_arg = -1L;                                     \
> > +		}                                                               \
> > +	}                                                                       \
> > +	__sysret_arg;                                                           \
> > +})
> 
> I also found during my tests that this one can return a build error if
> the arg has "const" in its type, due to the error assignment. We need
> to think about reworking it as a ternary evaluation, it will be more
> reliable even if less readable. But let's not change this now, I'm on
> the changelog already.
>

The __auto_type in new enough version does work well with 'const', but
for the old version, we need to restoring the 'long' type and the
conversion ;-(

> >  /* Functions in this file only describe syscalls. They're declared static so
> >   * that the compiler usually decides to inline them while still being allowed
> > @@ -94,7 +101,7 @@ void *sbrk(intptr_t inc)
> >  	if (ret && sys_brk(ret + inc) == ret + inc)
> >  		return ret + inc;
> >  
> > -	return (void *)__sysret(-ENOMEM);
> > +	return (void *)__sysret((unsigned long)-ENOMEM);
> 
> I noticed that one as well during my tests, but if we're purist, we're
> supposed to use (void*) for the cast and not (unsigned long), and it
> should allow to get rid of the outer cast.
>

To accept "void *", more conversions are required ...

A new 'ugly' version are ready for review soon, it compiles and get less size
on gcc-4.8 too ;-)

Thanks,
Zhangjin

> Regards,
> Willy
diff mbox series

Patch

diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h
index beddc3665d69..360dfc533814 100644
--- a/tools/include/nolibc/compiler.h
+++ b/tools/include/nolibc/compiler.h
@@ -22,4 +22,13 @@ 
 #  define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector")))
 #endif /* defined(__has_attribute) */
 
+/*
+ * from include/linux/compiler.h
+ *
+ * Whether 'type' is a signed type or an unsigned type. Supports scalar types,
+ * bool and also pointer types.
+ */
+#define is_signed_type(type)   (((type)(-1)) < (type)1)
+#define is_unsigned_type(type) (!is_signed_type(type))
+
 #endif /* _NOLIBC_COMPILER_H */
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h
index 56f63eb48a1b..8271302f79c4 100644
--- a/tools/include/nolibc/sys.h
+++ b/tools/include/nolibc/sys.h
@@ -35,15 +35,22 @@ 
  * (src/internal/syscall_ret.c) and glibc (sysdeps/unix/sysv/linux/sysdep.h)
  */
 
-static __inline__ __attribute__((unused, always_inline))
-long __sysret(unsigned long ret)
-{
-	if (ret >= (unsigned long)-MAX_ERRNO) {
-		SET_ERRNO(-(long)ret);
-		return -1;
-	}
-	return ret;
-}
+#define __sysret(arg)                                                           \
+({                                                                              \
+	__typeof__(arg) __sysret_arg = (arg);                                   \
+	if (is_signed_type(__typeof__(arg))) {                                  \
+		if (__sysret_arg < 0) {                                         \
+			SET_ERRNO(-(int)__sysret_arg);                          \
+			__sysret_arg = -1L;                                     \
+		}                                                               \
+	} else {                                                                \
+		if ((unsigned long)__sysret_arg >= (unsigned long)-MAX_ERRNO) { \
+			SET_ERRNO(-(int)__sysret_arg);                          \
+			__sysret_arg = -1L;                                     \
+		}                                                               \
+	}                                                                       \
+	__sysret_arg;                                                           \
+})
 
 /* Functions in this file only describe syscalls. They're declared static so
  * that the compiler usually decides to inline them while still being allowed
@@ -94,7 +101,7 @@  void *sbrk(intptr_t inc)
 	if (ret && sys_brk(ret + inc) == ret + inc)
 		return ret + inc;
 
-	return (void *)__sysret(-ENOMEM);
+	return (void *)__sysret((unsigned long)-ENOMEM);
 }