Compilando o kernel Linux com o LLVM/Clang
- por Sergio Prado
O LLVM é um projeto cujo objetivo é prover um conjunto modular de componentes para o desenvolvimento de compiladores e outras ferramentas de compilação como analisadores estáticos de código.
Um dos subprojetos do LLVM é o Clang, um compilador C/C++/Objective-C extremamente rápido, eficiente e mais “amigável” para exibir mensagens de warning e erro durante a compilação.
O projeto do compilador Clang é bem ativo e tem crescido bastante. Ainda não é possível substituir o GCC pelo Clang em muitos projetos, devido às dependências destes projetos com algumas diretivas e extensões específicas do GCC, mas espera-se que em alguns anos o Clang seja uma alternativa viável para compilar qualquer projeto escrito em C/C++.
E a Linux Foundation tem trabalhado para tornar isso realidade, pelo menos no que se refere ao kernel Linux, através do projeto LLVMLinux.
O objetivo deste projeto é possibilitar a compilação do kernel Linux com o Clang. Como o Linux utiliza bastante diretivas e extensões específicas do GCC, são muitas as alterações.
A idéia é integrar todas as alterações na àrvore mainline do kernel. Mas enquanto estas alterações passam pelo usual processo de aprovação da comunidade, está disponível no site do projeto um conjunto de patches para as arquiteturas suportadas (aarch64, arm, x86_64). No momento em que escrevo este artigo, são 47 patches no total, sendo 29 patches específicos para ARM.
Resolvi então compilar uma versão do kernel Linux para a Wandboard Quad com o Clang.
Baixei a última versão do Clang do site do projeto, descompactei em um diretório na minha máquina e coloquei o diretório dos binários do toolchain no PATH do sistema.
Para testar, ao invés de pegar um kernel puro e aplicar o patches, clonei a árvore git deles, que no momento em que escrevo este artigo é baseada na versão 3.19.0-rc4.
$ git clone git://git.linuxfoundation.org/llvmlinux/kernel.git |
Para compilar, é necessário definir as variáveis CC e HOSTCC com o nome do compilador clang. Outro detalhe importante é que, para compilar o kernel, o Clang não utiliza seu assembler interno, mas sim o assembler do GCC. Por isso o parâmetro CROSS_COMPILE está apontando para o prefixo do toolchain da Linaro, baseado no GCC.
$ make ARCH=arm imx_v6_v7_defconfig $ make -j8 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- HOSTCC=clang CC=clang zImage |
A compilação foi bem rápida, levou em torno de 2:21 minutos. Para comparar, compilei também com o GCC e obtive um resultado parecido.
O que me chamou a atenção foi a quantidade de warnings exibidos pelo Clang, e que passaram “batidos” pelo GCC (alguns deles abaixo). Realmente o analisador estático de código do Clang me parece ser bem melhor e mais eficiente que o GCC.
include/asm-generic/termios.h:28:6: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier] if (get_user(tmp, &termio->c_oflag) < 0) ^ scripts/mod/file2alias.c:119:55: note: expanded from macro 'ADD' sizeof(field) == 4 ? "%08X" : "", \ ^~ kernel/irq/manage.c:764:21: warning: address of array 'desc->irq_data.affinity' will always evaluate to 'true' [-Wpointer-bool-conversion] if (desc->irq_data.affinity) kernel/time/timer.c:55:26: warning: section does not match previous declaration [-Wsection] __visible u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES; mm/memblock.c:841:15: warning: implicit conversion from 'unsigned long long' to 'phys_addr_t' (aka 'unsigned int') changes value from 18446744073709551615 to 4294967295 [-Wconstant-conversion] r->base : ULLONG_MAX; ^~~~~~~~~~ |
Durante a compilação, tive um problema de compatibilidade com instruções assembly:
ARM Assembly: /tmp/traps-42effa.s: Assembler messages: /tmp/traps-42effa.s:411: Error: selected processor does not support ARM mode `wfe' /tmp/traps-42effa.s:650: Error: selected processor does not support ARM mode `sev' clang: error: assembler command failed with exit code 1 (use -v to see invocation) |
Para resolver, precisei desabilitar o suporte à ARMv6 no kernel (opção CONFIG_ARCH_MULTI_V6).
No final, foi gerada uma imagem de aproximadamente 5,4MB (10KB menor que a imagem gerada pelo GCC).
$ ls -l arch/arm/boot/zImage -rwxrwxr-x 1 sprado sprado 5424584 Mar 17 21:48 arch/arm/boot/zImage |
Testei a imagem em uma Wandboard Quad e funcionou sem problemas. O boot do kernel levou em torno de 3s.
[ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Linux version 3.19.0-rc4-15902-g230b22d72454 (sprado@sprado-desktop) (clang version 3.6.0 (tags/RELEASE_360/final)) #2 SMP Tue Mar 17 21:48:34 BRT 2015 [ 0.000000] CPU: ARMv7 Processor [412fc09a] revision 10 (ARMv7), cr=10c5387d [ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache ... [ 2.936953] Freeing unused kernel memory: 372K (8093b000 - 80998000) |
Segundo artigos e benchmarks disponíveis na Internet, o Clang é capaz de compilar mais rápido e também gerar um código que executa mais rápido. Comparando com o GCC, não senti tanta diferença na performance, pelo menos no que se refere à compilação do kernel.
O que realmente me chamou a atenção foi seu analisador estático de código, que identificou possíveis fontes de problemas que mesmo o GCC com a flag -Wall não foi capaz de identificar. Isso ajuda bastante a melhorar a qualidade do código.
Vamos ver como o projeto vai evoluir.
Happy compiling!
Sergio Prado