https://maskray.me/blog/2021-10-10-when-can-glibc-be-built-with-clang MaskRay Home Archives Presentations [github] [twitter] [zhihu] [weibo] [ ] 2021-10-10 When can glibc be built with Clang? In September, I wrote "So, dear glibc, will you be happy with my sending Clang patches?" in Build glibc with LLD 13. We have come to a turning point. In Linux Plumbers Conference 2021, at the glibc Birds of a Feather session, I asked the Clang buildability question to the glibc stewards. (Interlude: I did not realize that I should attend the conference (it was a great opportunity from an outlier to meet some glibc folks). In Tuesday, Wei Wu (lazyparser) kindly gave me his account: "Xiang Qu Can Jia LPCYao ?Wo Hui Yi Tai Duo Liao Jin Tian Can Jia Bu Guo Lai ". I happily accepted it and typed the question during the glibc session.) So I got positive responses. "Carlos: Yes, we could be happy with clang buildability." "Joseph: Patches should be split into logical changes." This is really great news! My unnesting patch had sat there for a while and I was unsure about the Clang buildability interest. Of course, every change needs to be assessed where they belong to, LLVM/Clang or glibc. My interest in Clang/LLD buildability did not arise suddenly. It has been multiple years. I made my first patch improving LLD's glibc buildability in June 2018. Probably since June 2019, I pushed first LLVM fix mproving glibc buildability [MC][ELF] Don't create relocations with section symbols for STB_LOCAL ifunc. I have sporadic contribution since then. Today I think there is not too much to improve on LLVM/Clang's side, but many need glibc adapatation. As a rough estimate, we may need 30 patches to fix glibc build on x86-64 for the --disable-werror configuration. Fixing other popular architectures or all tests will take more. Adrian Ratiu mentioned that shebs/clangify exists in A tale of two toolchains and glibc. The branch in the 2018 era has about 30 commits. --------------------------------------------------------------------- Below I will introduce the main challenges (GCC nested functions, asm label after first use, Clang's less permissive warnings) and a selective set of miscellaneous changes. GCC nested functions glibc's dynamic loader (often called ld.so) implementation (elf/ plus arch-specific code in sysdeps/*/) made extensive use of a feature called "nested functions". https://gcc.gnu.org/onlinedocs/gcc/ Nested-Functions.html says A nested function is a function defined inside another function. Nested functions are supported as an extension in GNU C, but are not supported by GNU C++. This is a no-go for Clang which does not support this feature (error: function definition is not allowed here). The dynamic loader usage actually impacted readability and debuggability, so people agreed that the usage should simply be removed. As a worst example, the dynamic loader had a function like: 1 #ifndef RESOLVE_MAP 2 static 3 #else 4 auto 5 #endif 6 inline void __attribute__ ((unused, always_inline)) 7 elf_get_dynamic_info (struct link_map *l) 8 { Its file could be included twice, one with RESOLVE_MAP defined and one without. It was difficult to understand the code intention. As of 2021-10-07, the usage in the dynamic loader has been removed by my elf: Avoid nested functions in the loader [BZ #27220]. Remaining usage include: * nss/makedb.c:add_key * posix/regcomp.c:{seek_collating_symbol_entry,...} asm label after first use In GCC and Clang, an asm label can change the symbol name for a definition or a non-definition declaration. glibc makes heavy use of this feature even in public headers. #pragma redefine_extname is a similar feature which can change the symbol name. In Clang, #pragma redefine_extname is converted to an asm label and shares code with the asm label implementation. Clang before 12.0.0 had an issue that a built-in function ignored the asm label. I fixed it and made some notes on https://maskray.me/blog/ 2020-10-15-intra-call-and-libc-symbol-renaming (TODO: translate it into English). 1 typedef unsigned long size_t; 2 3 #ifdef ASM_LABEL 4 extern void *memcpy(void *, const void *, size_t) asm("__GI_memcpy"); 5 #else 6 #pragma redefine_extname memcpy __GI_memcpy // before the first use, works 7 extern void *memcpy(void *, const void *, size_t); 8 #endif 9 10 void *test_memcpy(void *dst, const void *src, size_t n) { 11 // Clang < 12: callq memcpy 12 // Clang >= 12: callq __GI_memcpy 13 return memcpy(dst, src, n); 14 } Unfortunately, there is a limitation. If a #pragma redefine_extname is declared after the first use of the symbol, the rename operation will be silently ignored. 1 // a.c 2 typedef unsigned long size_t; 3 4 extern void *memcpy(void *, const void *, size_t); 5 6 void *test_memcpy(void *dst, const void *src, size_t n) { 7 return memcpy(dst, src, n); 8 } 9 10 #ifdef ASM_LABEL 11 extern void *memcpy(void *, const void *, size_t) asm("__GI_memcpy"); 12 #else 13 #pragma redefine_extname memcpy __GI_memcpy // before the first use, works 14 #endif If an asm label applies to a redeclaration after the first use, Clang will kindly give an error to notify that it cannot handle the situation: 1 % clang a.c -S -o - | grep callq 2 callq memcpy@PLT 3 % clang a.c -S -o - -DASM_LABEL 4 a.c:18:14: error: cannot apply asm label to function after its first use 5 extern void *memcpy(void *, const void *, size_t) asm("__GI_memcpy"); 6 ^ ~~~~~~~~~~~~~ 7 1 error generated. glibc makes extensive usage of hidden_proto and libc_hidden_proto, where such problems may occur in many places. It seems that disabling __USE_EXTERN_INLINES can bypass issues with some symbols. _Float128 long double and float128 support in GCC is a huge mess. In its PowerPC port, long double has 3 mangling schemes: * -mlong-double-64: e * -mlong-double-128 -mabi=ibmlongdouble: g * -mlong-double-128 -mabi=ieeelongdouble: u9__ieee128 (gcc <= 8.1: U10__float128) In its x86 port, two different long double configurations share the same mangling code. * -mlong-double-64: e * -mlong-double-80: e * -mlong-double-128: g (I fixed some Clang issues in https://reviews.llvm.org/D64276.) __float128 has the same mangling code with 128-bit long double but is considered a distinct type in both C and C++. So if you have two overloads foo(__float128) and foo(long double), you will get a compiler/linker error due to conflicting symbols. _Float128 is identical to __float128. _Float128 is available in C mode but not in C++ mode. When a future standard C++ introduces a float128 type, it has to be yet another new type to be different from the 128-bit long double. 1 // C 2 #include 3 4 int main() { 5 // _Float128 = __float128 6 puts(_Generic((_Float128)0, __float128: "same", default: "different")); 7 // _Float128 != long double, even in -mlong-double-128 mode. 8 puts(_Generic((_Float128)0, long double: "same", default: "different")); 9 } Anyhow, glibc builds some float128 code on some PowerPC, x86, and perhaps a few other architectures. Clang doesn't support _Float128. Falling back to typedef __float128 _Float128 is probably a good choice. 1 --- a/sysdeps/x86/bits/floatn.h 2 +++ b/sysdeps/x86/bits/floatn.h 3 @@ -29,5 +29,6 @@ 4 #if (defined __x86_64__ \ 5 ? __GNUC_PREREQ (4, 3) \ 6 - : (defined __GNU__ ? __GNUC_PREREQ (4, 5) : __GNUC_PREREQ (4, 4))) 7 + : (defined __GNU__ ? __GNUC_PREREQ (4, 5) : __GNUC_PREREQ (4, 4))) \ 8 + || defined __clang__ 9 # define __HAVE_FLOAT128 1 10 #else 11 @@ -88,5 +89,5 @@ typedef __float128 _Float128; 12 13 /* __builtin_huge_valf128 doesn't exist before GCC 7.0. */ 14 -# if !__GNUC_PREREQ (7, 0) 15 +# if !__GNUC_PREREQ (7, 0) && !defined __clang__ 16 # define __builtin_huge_valf128() ((_Float128) __builtin_huge_val ()) 17 # endif 18 @@ -97,5 +98,5 @@ typedef __float128 _Float128; 19 attempts to use _Float128 sNaNs will not work properly with older 20 compilers. */ 21 -# if !__GNUC_PREREQ (7, 0) 22 +# if !__GNUC_PREREQ (7, 0) && !defined __clang__ 23 # define __builtin_copysignf128 __builtin_copysignq 24 # define __builtin_fabsf128 __builtin_fabsq 25 @@ -109,5 +110,5 @@ typedef __float128 _Float128; 26 been a __builtin_signbitf128 in GCC and the type-generic builtin is 27 only available since GCC 6. */ 28 -# if !__GNUC_PREREQ (6, 0) 29 +# if !__GNUC_PREREQ (6, 0) && !defined __clang__ 30 # define __builtin_signbitf128 __signbitf128 31 # endif -fheinous-gnu-extensions This funny option name has an associated comment for CheckAsmLValue: 1 GNU C has an extremely ugly extension whereby they silently ignore "noop" casts in places where an lvalue is required by an inline asm. 2 We emulate this behavior when -fheinous-gnu-extensions is specified, but provide a strong guidance to not use it. 3 This method checks to see if the argument is an acceptable l-value and returns false if it is a case we can handle. This requires some fixes, e.g. [PATCH] include/longlong.h: Remove incorrect lvalue to rvalue conversion from asm output constraints: 1 --- a/stdlib/longlong.h 2 +++ b/stdlib/longlong.h 3 @@ -493,6 +493,6 @@ extern UDItype __umulsidi3 (USItype, USItype); 4 #define add_ssaaaa(sh, sl, ah, al, bh, bl) \ 5 __asm__ ("add{q} {%5,%1|%1,%5}\n\tadc{q} {%3,%0|%0,%3}" \ 6 - : "=r" ((UDItype) (sh)), \ 7 - "=&r" ((UDItype) (sl)) \ 8 + : "=r" (sh), \ 9 + "=&r" (sl) \ 10 : "%0" ((UDItype) (ah)), \ 11 "rme" ((UDItype) (bh)), \ 12 @@ -501,6 +501,6 @@ extern UDItype __umulsidi3 (USItype, USItype); 13 #define sub_ddmmss(sh, sl, ah, al, bh, bl) \ 14 __asm__ ("sub{q} {%5,%1|%1,%5}\n\tsbb{q} {%3,%0|%0,%3}" \ 15 - : "=r" ((UDItype) (sh)), \ 16 - "=&r" ((UDItype) (sl)) \ 17 + : "=r" (sh), \ 18 + "=&r" (sl) \ 19 : "0" ((UDItype) (ah)), \ 20 "rme" ((UDItype) (bh)), \ 21 @@ -509,12 +509,12 @@ extern UDItype __umulsidi3 (USItype, USItype); 22 #define umul_ppmm(w1, w0, u, v) \ 23 __asm__ ("mul{q} %3" \ 24 - : "=a" ((UDItype) (w0)), \ 25 - "=d" ((UDItype) (w1)) \ 26 + : "=a" (w0), \ 27 + "=d" (w1) \ 28 : "%0" ((UDItype) (u)), \ 29 "rm" ((UDItype) (v))) 30 #define udiv_qrnnd(q, r, n1, n0, dv) \ 31 __asm__ ("div{q} %4" \ 32 - : "=a" ((UDItype) (q)), \ 33 - "=d" ((UDItype) (r)) \ 34 + : "=a" (q), \ 35 + "=d" (r) \ 36 : "0" ((UDItype) (n0)), \ 37 "1" ((UDItype) (n1)), \ LLVM's integrated assembler On most targets, Clang uses LLVM's integrated assembler. It lacks support for some unpopular features and may be stricter than GNU as in some cases. 1 % cat a.s 2 bndmov %bnd0,(%rsp) 3 bnd 4 jmp *%r11 5 % as a.s 6 % clang -c a.s 7 a.s:2:1: error: invalid instruction mnemonic 'bnd' 8 bnd 9 ^~~ I attempted to improve sysdeps/x86_64/configure.ac but was asked to just remove the deprecated Intel MPX support. So I sent [PATCH] elf: Remove Intel MPX support (lazy PLT, ld.so profile, and LD_AUDIT). As another example, sysdeps/powerpc/powerpc64/le/power10/memmove.S uses the directive .machine power9. For some other x86 projects I noticed that GNU as' .arch supports a very wide range of .arch operands. In LLVM, *AsmParser files do not do a good job tracking what features are needed to assemble instructions. It is also tricky to recognize all GNU as supported operands. The ISA feature compatibility check, while looks nice, actually provides a lower value. So I patched LLVM's x86 and PowerPC targets to ignore .arch and .machine operands. A few sysdeps/i386/fpu/*.S files use .tfloat (80-bit extended precision floating point) which is unsupported by LLVM's integrated assembler. Inline asm parsing While GCC does minimal parsing of an inline asm statement (https:// gcc.gnu.org/onlinedocs/gcc/Size-of-an-asm.html), Clang parses everything. glibc uses an inline asm trick to get the constant value of a macro (e.g. a Linux syscall number). @@@ is invalid asm, so Clang rejects it. 1 % cat a.c 2 #include 3 #include 4 #include 5 6 int main() { 7 asm ("@@@%0@@@" :: "i"((long int)(X))); 8 } 9 % gcc -S -DX=__NR_mmap a.c -o - | grep @@@ 10 @@@$9@@@ 11 % clang -S -DX=__NR_mmap a.c -o /dev/null 12 a.c:6:8: error: unexpected token at start of statement 13 asm ("@@@%0@@@" :: "i"((long int)(X))); 14 ^ 15 :1:2: note: instantiated into assembly here 16 @@@$9@@@ 17 ^ 18 1 error generated. The fix is simple. We can just surround the invalid part with a pair of /* */. I avoid # because it may have weird interpretation on some exotic architecture (https://sourceware.org/binutils/docs/as/ Comments.html#Comments). More rigid semantic analysis and finicky warnings _Static_assert The first operand of a _Static_assert declaration must be an integer constant expression. C11 defines operands which are required to make up an integer constant expression. 6 An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof or _Alignof operator. 10 An implementation may accept other forms of constant expressions. An implementation may or may not accept a const object (e.g. const size_t allocation_size = 32768;) as an operand. Clang is simple: a const object cannot be used as an operand in a constant expression. GCC is inconsistent (PR102502): * -O2: accepted * -O2 -Wpedantic: warning: expression in static assertion is not an integer constant expression [-Wpedantic] * -O0: error: expression in static assertion is not constant 1 % cat reduce.i 2 const int __alloc_dir_allocation_size = 8; 3 void __alloc_dir() { _Static_assert(__alloc_dir_allocation_size, ""); } 4 5 % gcc reduce.i -c -std=c11 6 reduce.i: In function '__alloc_dir': 7 reduce.i:2:37: error: expression in static assertion is not constant 8 2 | void __alloc_dir() { _Static_assert(__alloc_dir_allocation_size, ""); } 9 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 % gcc reduce.i -c -std=c11 -O1 11 % gcc reduce.i -c -std=c11 -O2 12 % gcc reduce.i -c -std=c11 -O2 -Wpedantic 13 reduce.i: In function '__alloc_dir': 14 reduce.i:2:37: warning: expression in static assertion is not an integer constant expression [-Wpedantic] 15 2 | void __alloc_dir() { _Static_assert(__alloc_dir_allocation_size, ""); } 16 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 17 18 % clang reduce.i -c -std=c11 19 reduce.i:2:37: error: static_assert expression is not an integral constant expression 20 void __alloc_dir() { _Static_assert(__alloc_dir_allocation_size, ""); } 21 ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 22 1 error generated. sysdeps/unix/sysv/linux/opendir.c has such an issue, which will be fixed by [PATCH v2] linux: Fix a non-constant expression in _Static_assert.. Share Comments * clang * glibc Older Competitive programming in Nim Please enable JavaScript to view the comments powered by Disqus. Popular Tag Cloud adc ai9 algorithm asc automaton awesome bctf binary binutils bmc build system c c++ ccls cgc chroot clang codinsanity coffee script compiler computer security contest csv ctf data structure debug defcon desktop docker elf emacs email emoji emscripten event expect ext4 feeds firmware floating point forensics freebsd game gcc gentoo glibc graph drawing gtk hanoi haskell hpc inotify ipsec irc isc j javascript josephus problem jq kernel kythe ld leetcode linker linux lld llvm lsp makefile math maze mirror ml mutt n-body network nginx nim nlp node.js noip notmuch npm ocaml offlineimap oi oj openwrt parallel parser generator perl presentation puzzle python qq radare2 regex regular expression reverse engineering review router rtld ruby ructfe scheme search security shell ssh stringology student festival puzzle suffix array suffix automaton summary suricata telegram telegramircd terminal tls traversal tree trendmicro udev unicode usb vim vpn vte wargame web analytics webqqircd website wechat wechatircd window manager xbindkeys xmonad yanshi Blogroll * BYVoid * fqj1994 * ppwwyyxx (c) 2021 MaskRay Powered by Hexo Home Archives Presentations