Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Builtins in Clang not so builtin?

Tags:

c

gcc

clang

If I have the following in strlen.c:

int call_strlen(char *s) {
  return __builtin_strlen(s);
}

And then compile it with both gcc and clang like this:

gcc -c -o strlen-gcc.o strlen.c

clang -c -o strlen-clang.o strlen.c

I am surprised to see that strlen-clang.o contains a reference to "strlen", whereas gcc has expectedly inlined the function and has no such reference. (see objdumps below). Is this a bug in clang? I have tested it in several versions of the clang compiler, including 3.8.

Edit: the reason this is important for me is that I'm linking with -nostdlib, and the clang-compiled version gives me a link error that strlen is not found.

Clang

@> objdump -d strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0: 55                    push   %rbp
   1: 48 89 e5              mov    %rsp,%rbp
   4: 48 83 ec 10           sub    $0x10,%rsp
   8: 48 89 7d f8           mov    %rdi,-0x8(%rbp)
   c: 48 8b 7d f8           mov    -0x8(%rbp),%rdi
  10: e8 00 00 00 00        callq  15 <call_strlen+0x15>
  15: 89 c1                 mov    %eax,%ecx
  17: 89 c8                 mov    %ecx,%eax
  19: 48 83 c4 10           add    $0x10,%rsp
  1d: 5d                    pop    %rbp
  1e: c3                    retq   


@> objdump -t strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 g     F .text  000000000000001f call_strlen
0000000000000000         *UND*  0000000000000000 strlen

GCC

@> objdump -d strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   8:   48 8b 45 f8             mov    -0x8(%rbp),%rax
   c:   48 c7 c1 ff ff ff ff    mov    $0xffffffffffffffff,%rcx
  13:   48 89 c2                mov    %rax,%rdx
  16:   b8 00 00 00 00          mov    $0x0,%eax
  1b:   48 89 d7                mov    %rdx,%rdi
  1e:   f2 ae                   repnz scas %es:(%rdi),%al
  20:   48 89 c8                mov    %rcx,%rax
  23:   48 f7 d0                not    %rax
  26:   48 83 e8 01             sub    $0x1,%rax
  2a:   5d                      pop    %rbp
  2b:   c3                      retq   

@> objdump -t strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     F .text  000000000000002c call_strlen
like image 937
brooks94 Avatar asked Jul 27 '16 13:07

brooks94


1 Answers

Just to get optimisation out of the way:

With clang -O0:

t.o:
(__TEXT,__text) section
_call_strlen:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    subq    $0x10, %rsp
0000000000000008    movq    %rdi, -0x8(%rbp)
000000000000000c    movq    -0x8(%rbp), %rdi
0000000000000010    callq   _strlen
0000000000000015    movl    %eax, %ecx
0000000000000017    movl    %ecx, %eax
0000000000000019    addq    $0x10, %rsp
000000000000001d    popq    %rbp
000000000000001e    retq

With clang -O3

t.o:
(__TEXT,__text) section
_call_strlen:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    popq    %rbp
0000000000000005    jmp _strlen

Now, onto the problem:

The clang documentation claims that clang support all GCC-supported builtins.
However, the GCC documentation seems to treat builtin functions and the names of their library equivalents as synonyms:

Both forms have the same type (including prototype), the same address (when their address is taken), and the same meaning as the C library functions [...].

Also it does not guarantee a builtin function with a library equivalent (as is the case with strlen) to indeed get optimised:

Many of these functions are only optimized in certain cases; if they are not optimized in a particular case, a call to the library function is emitted.

Further, the clang internals manual mentions __builtin_strlen only once:

  • __builtin_strlen and strlen: These are constant folded as integer constant expressions if the argument is a string literal.

Other than that they seem to make no promises.

Since in your case the argument to __builtin_strlen is not a string literal, and since the GCC documentation allows for calls to builtin functions to be converted to library function calls, clang's behaviour seems perfectly valid.

A "patch for review" on the clang developers mailing list also says:

[...] It will still fall back to runtime use of library strlen, if compile-time evaluation is not possible/required [...].

That was in 2012, but the text indicates that at least back then, only compile-time evaluation was supported.

Now, I see two options:

  • If you only need to compile the program yourself and then use and/or distribute it, I suggest you simply use gcc.
  • If you need others to be able to compile your code under both gcc and clang, I suggest adding a C library as a dependency for static linking.

I strongly advise against rolling your own implementations of standard library functions, even for seemingly simple cases (if you disagree, try writing up your own strlen implementation, then compare it to the glibc one).

like image 124
Siguza Avatar answered Sep 27 '22 21:09

Siguza