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 and let __sysret() be able to accept the (void *) type of argument:
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);
Fourthly, to further workaround the argument type with 'const', must use __auto_type in a new enough version or use 'long' as before.
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/ Link: https://lore.kernel.org/lkml/20230806134348.GA19145@1wt.eu/ Signed-off-by: Zhangjin Wu falcon@tinylab.org ---
v2 here is further fix up argument with 'const' in the type and also support "void *" argument, v1 is [1].
Tested on many architectures (i386, x86_64, mips, ppc64) and gcc version (from gcc 4.8-13.1.0), compiles well without any warning and errors and also with smaller size.
[1]: https://lore.kernel.org/lkml/20230806131921.52453-1-falcon@tinylab.org/
--- tools/include/nolibc/sys.h | 52 ++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 56f63eb48a1b..9c7448ae19e2 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -35,15 +35,45 @@ * (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; -} +/* + * Whether 'type' is a signed type or an unsigned type. Supports scalar types, + * bool and also pointer types. (from include/linux/compiler.h) + */ +#define __is_signed_type(type) (((type)(-1)) < (type)1) + +/* __auto_type is used instead of __typeof__ to workaround the build error + * 'error: assignment of read-only variable' when the argument has 'const' in + * the type, but __auto_type is a new feature from newer version and it only + * work with 'const' from gcc 11.0 (__GXX_ABI_VERSION = 1016) + * https://gcc.gnu.org/legacy-ml/gcc-patches/2013-11/msg01378.html + */ + +#if __GXX_ABI_VERSION < 1016 +#define __typeofdecl(arg) long +#define __typeofconv1(arg) (long) +#define __typeofconv2(arg) (long) +#else +#define __typeofdecl(arg) __auto_type +#define __typeofconv1(arg) +#define __typeofconv2(arg) (__typeof__(arg)) +#endif + +#define __sysret(arg) \ +({ \ + __typeofdecl(arg) __sysret_arg = __typeofconv1(arg)(arg); \ + if (__is_signed_type(__typeof__(arg))) { \ + if (__sysret_arg < 0) { \ + SET_ERRNO(-(long)__sysret_arg); \ + __sysret_arg = __typeofconv2(arg)(-1L); \ + } \ + } else { \ + if ((unsigned long)__sysret_arg >= (unsigned long)-MAX_ERRNO) { \ + SET_ERRNO(-(long)__sysret_arg); \ + __sysret_arg = __typeofconv2(arg)(-1L); \ + } \ + } \ + (__typeof__(arg))__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 +124,7 @@ void *sbrk(intptr_t inc) if (ret && sys_brk(ret + inc) == ret + inc) return ret + inc;
- return (void *)__sysret(-ENOMEM); + return __sysret((void *)-ENOMEM); }
@@ -682,7 +712,7 @@ void *sys_mmap(void *addr, size_t length, int prot, int flags, int fd, static __attribute__((unused)) void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { - return (void *)__sysret((unsigned long)sys_mmap(addr, length, prot, flags, fd, offset)); + return __sysret(sys_mmap(addr, length, prot, flags, fd, offset)); }
static __attribute__((unused))
Hi, Willy
[...] 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/ Link: https://lore.kernel.org/lkml/20230806134348.GA19145@1wt.eu/ Signed-off-by: Zhangjin Wu falcon@tinylab.org
v2 here is further fix up argument with 'const' in the type and also support "void *" argument, v1 is [1].
Tested on many architectures (i386, x86_64, mips, ppc64) and gcc version (from gcc 4.8-13.1.0), compiles well without any warning and errors and also with smaller size.
tools/include/nolibc/sys.h | 52 ++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 56f63eb48a1b..9c7448ae19e2 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -35,15 +35,45 @@
- (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;
-} +/*
- Whether 'type' is a signed type or an unsigned type. Supports scalar types,
- bool and also pointer types. (from include/linux/compiler.h)
- */
+#define __is_signed_type(type) (((type)(-1)) < (type)1)
+/* __auto_type is used instead of __typeof__ to workaround the build error
- 'error: assignment of read-only variable' when the argument has 'const' in
- the type, but __auto_type is a new feature from newer version and it only
- work with 'const' from gcc 11.0 (__GXX_ABI_VERSION = 1016)
- */
+#if __GXX_ABI_VERSION < 1016 +#define __typeofdecl(arg) long +#define __typeofconv1(arg) (long) +#define __typeofconv2(arg) (long) +#else +#define __typeofdecl(arg) __auto_type +#define __typeofconv1(arg) +#define __typeofconv2(arg) (__typeof__(arg)) +#endif
With nolibc-test, we did more tests.
for ppc64le (long better):
// __auto_type $ size nolibc-test text data bss dec hex filename 27736 8 80 27824 6cb0 nolibc-test
// long $ size nolibc-test text data bss dec hex filename 27612 8 80 27700 6c34 nolibc-test
for ppc64 (long better):
// __auto_type $ size nolibc-test text data bss dec hex filename 27136 1880 80 29096 71a8 nolibc-test
// long $ size nolibc-test text data bss dec hex filename 27012 1880 80 28972 712c nolibc-test
A further test on x86_64 (__auto_type better):
// __auto_type $ size nolibc-test text data bss dec hex filename 22206 8 88 22302 571e nolibc-test
// long $ size nolibc-test text data bss dec hex filename 22347 8 88 22443 57ab nolibc-test
And i386 (almost the same):
// __auto_type $ size nolibc-test text data bss dec hex filename 19718 4 52 19774 4d3e nolibc-test
// long $ size nolibc-test text data bss dec hex filename 19717 4 52 19773 4d3d nolibc-test
arm64 (__auto_type better): // __auto_type $ size nolibc-test text data bss dec hex filename 25764 8 80 25852 64fc nolibc-test
// long $ size nolibc-test text data bss dec hex filename 26004 8 80 26092 65ec nolibc-test
arm (the same):
// __auto_type $ size nolibc-test text data bss dec hex filename 19595 0 52 19647 4cbf nolibc-test
// long $ size nolibc-test text data bss dec hex filename 19595 0 52 19647 4cbf nolibc-test
riscv64 (__auto_type better):
// __auto_type $ size nolibc-test text data bss dec hex filename 21814 8 80 21902 558e nolibc-test
// long $ size nolibc-test text data bss dec hex filename 21912 8 80 22000 55f0 nolibc-test
s390 (__auto_type better):
// __auto_type $ size nolibc-test text data bss dec hex filename 22302 0 80 22382 576e nolibc-test
// long $ size nolibc-test text data bss dec hex filename 22438 0 80 22518 57f6 nolibc-test
As a summary, in nolibc-test, for 32-bit architectures, __auto_type has the same as long, for 64-bit architecture, __auto_type has less size than long, only ppc64/ppc64le has reverse result.
BR, Zhangjin
+#define __sysret(arg) \ +({ \
- __typeofdecl(arg) __sysret_arg = __typeofconv1(arg)(arg); \
- if (__is_signed_type(__typeof__(arg))) { \
if (__sysret_arg < 0) { \
SET_ERRNO(-(long)__sysret_arg); \
__sysret_arg = __typeofconv2(arg)(-1L); \
} \
- } else { \
if ((unsigned long)__sysret_arg >= (unsigned long)-MAX_ERRNO) { \
SET_ERRNO(-(long)__sysret_arg); \
__sysret_arg = __typeofconv2(arg)(-1L); \
} \
- } \
- (__typeof__(arg))__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 +124,7 @@ void *sbrk(intptr_t inc) if (ret && sys_brk(ret + inc) == ret + inc) return ret + inc;
- return (void *)__sysret(-ENOMEM);
- return __sysret((void *)-ENOMEM);
} @@ -682,7 +712,7 @@ void *sys_mmap(void *addr, size_t length, int prot, int flags, int fd, static __attribute__((unused)) void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) {
- return (void *)__sysret((unsigned long)sys_mmap(addr, length, prot, flags, fd, offset));
- return __sysret(sys_mmap(addr, length, prot, flags, fd, offset));
} static __attribute__((unused)) -- 2.25.1
linux-kselftest-mirror@lists.linaro.org