1 pontos por GN⁺ 2025-01-25 | 1 comentários | Compartilhar no WhatsApp
  • Wild é um linker com foco em links muito rápidos durante o desenvolvimento iterativo; o link incremental ainda não foi implementado, mas mesmo sem ele já é rápido
  • O ponto de partida do Wild foi o fato de o mold, um linker rápido já existente, não fazer link incremental nem ter planos para isso; o Wild foi escrito em Rust com o objetivo de lidar com a complexidade do link incremental
  • As plataformas atualmente suportadas são x86-64, ARM64, RISC-V, LoongArch64 e PPC64LE no Linux; ele suporta binários estáticos, static-PIE, binários com link dinâmico, objetos compartilhados, proc-macros de Rust, informações de depuração e mais
  • Pode ser usado como drop-in replacement, sendo chamado como um linker existente a partir de GCC ou Clang, e também pode ser integrado com Cargo, sistemas de build C/C++ e o wild-action para CI
  • Ainda não há suporte para link incremental, linker scripts mais complexos, Mach-O nem Windows; para confirmar se o link foi realmente feito com Wild, é preciso verificar a string Linker: Wild version... no binário com readelf ou strings

Objetivos e posicionamento do Wild

  • Wild é um linker voltado a links muito rápidos durante o desenvolvimento iterativo
  • O plano de longo prazo é implementar link incremental, mas isso ainda não foi feito
  • Mesmo sem link incremental, o desempenho atual de link não incremental já é rápido
  • O mold já é muito rápido, mas não faz link incremental, e o autor declarou que não pretende implementá-lo; esse foi o motivo para o Wild surgir como um linker separado
  • O Wild é escrito em Rust, com a expectativa de que Rust ajude a lidar com a complexidade do link incremental

Como instalar

  • Os tarballs de release podem ser baixados na página de releases; basta descompactar e copiar o binário wild para um local presente no PATH
  • Se você tiver cargo-binstall, pode instalar com o comando abaixo
cargo binstall wild-linker
  • O comando de instalação via Homebrew é o seguinte
brew install wild-linker/wild/wild
  • Para compilar a release mais recente do crates.io, use o comando abaixo
cargo install --locked wild-linker
  • Para instalar o código mais recente ainda não lançado do Git, use o comando abaixo
cargo install --locked --bin wild --git https://github.com/wild-linker/wild.git wild-linker
  • A versão estável do Wild no Nixpkgs pode ser usada via pkgs.useWildLinker pkgs.stdenv
  • Para a revisão Git instável mais recente, consulte o documento nix/nix.md

Como usar como linker padrão

  • O Wild foi projetado como um drop-in replacement, podendo ser chamado da mesma forma que GCC ou Clang chamam um linker
  • As opções são as seguintes
    • A opção específica do Clang --ld-path=wild
    • -fuse-ld=wild no GCC 16.1 ou superior e no Clang
      • No Clang, é necessário um binário ld.wild ou um link simbólico com esse nome
    • A opção amplamente suportada -B <path>
      • <path> deve ser o diretório que contém ld apontando para wild
  • Rust e Cargo

    • Em ~/.cargo/config.toml, é possível usar Clang junto com --ld-path=wild
    [target.x86_64-unknown-linux-gnu]
    linker = "clang"
    rustflags = ["-Clink-arg=--ld-path=wild"]
    
    • Ou usar -fuse-ld=wild
    [target.x86_64-unknown-linux-gnu]
    
    # linker = "clang" # Uncomment this line if your GCC is older than version 16.
    rustflags = ["-Clink-arg=-fuse-ld=wild"]
    
  • Sistemas de build C/C++

    • Em autotools, CMake, meson e afins, normalmente basta configurar LDFLAGS
    export LDFLAGS="${LDFLAGS} -fuse-ld=wild"
    
    • Em versões antigas do GCC, é possível criar um link simbólico ld apontando para wild e passar esse diretório com -B
    ln -s /usr/bin/wild /tmp/ld
    
    export CFLAGS="${CFLAGS} -B/tmp"
    export CXXFLAGS="${CXXFLAGS} -B/tmp"
    export LDFLAGS="${LDFLAGS} -B/tmp"
    
    • Como esses sistemas de build podem ser complexos, recomenda-se verificar com readelf se o Wild foi realmente usado para linkar o binário final
  • Configuração do Cargo para Illumos

    • No Illumos, é preciso informar o caminho absoluto do Clang e também passar o Wild com caminho absoluto
    • Se o caminho absoluto do Wild não for fornecido, a chamada pode ser silenciosamente delegada para GNU ld ou Sun ld
    [target.x86_64-unknown-illumos]
    
    # Absolute path to clang - on OmniOS this is likely something like /opt/ooce/bin/clang.
    linker = "/usr/bin/clang"
    
    rustflags = [
    
    # Will silently delegate to GNU ld or Sun ld unless the absolute path to Wild is provided.
        "-Clink-arg=-fuse-ld=/absolute/path/to/wild"
    ]
    

Estado de suporte e itens ainda não suportados

  • As plataformas e arquiteturas atualmente suportadas são as seguintes
    • x86-64 no Linux

    • ARM64 no Linux

    • RISC-V (riscv64gc) no Linux

      • LoongArch64 no Linux
      • Suporte em estágio inicial
      • PPC64LE no Linux
      • Suporte em estágio inicial
      • As funcionalidades que funcionam hoje são as seguintes, embora com a ressalva de que ainda pode haver bugs
      • Saída de binários estáticos não relocáveis
      • Saída de binários estáticos position-independent, ou seja, static-PIE
      • Saída de binários com link dinâmico
      • Saída de arquivos de objeto compartilhado .so
      • Rust proc-macro linkado com Wild
      • Testes aprovados para a maioria dos crates mais baixados do crates.io
      • Informações de depuração
      • Suporte a GNU jobserver
      • Suporte parcial a linker script
      • Para mais detalhes, consulte a matriz de suporte a linker scripts
      • Linker plugin LTO
      • problemas conhecidos
      • Os grandes itens ainda não implementados, em ordem aproximada de prioridade atual, são os seguintes
    • Link incremental

      • Linker scripts mais complexos
      • Suporte a Mach-O
      • Suporte a Windows

Como verificar se o Wild foi usado

  • readelf pode ser instalado pelo pacote binutils, e o comando abaixo mostra as strings da seção .comment
readelf --string-dump .comment my-executable
  • Se aparecer uma linha como a seguinte, o binário foi linkado com Wild
Linker: Wild version 0.1.0
  • strings também pode ser usado a partir do pacote binutils, com o comando abaixo
strings my-executable | grep 'Linker:'

Benchmarks e alvos de teste

  • O objetivo final do Wild é se tornar um linker muito rápido por meio de link incremental
  • Ele também busca tornar o link não incremental e o link inicial com o modo incremental ativado o mais rápidos possível
  • Todos os benchmarks são executados com a saída em tmpfs
  • A forma de executar os benchmarks está descrita em BENCHMARKING.md
  • Os sistemas usados nos benchmarks são os seguintes
  • Os benchmarks de exemplo incluem tempo de link e uso de memória do Chromium, tempo de link do librustc-driver, tempo de link do próprio Wild e tempo de link do rust-analyzer no Raspberry Pi 5

Exemplos de link de código Rust e informações do projeto

  • Um exemplo de uso do Wild para compilar e testar crates com cargo test é o seguinte
  • Esse comando foi executado com sucesso em alguns crates populares, como ripgrep, serde, tokio, rand e bitflags
  • O binário wild precisa estar no PATH, e é necessário ter o Clang instalado porque o GCC não permite usar um linker arbitrário
RUSTFLAGS="-Clinker=clang -Clink-args=--ld-path=wild" cargo test
  • Se o link simbólico ld.wild apontar para wild, a forma abaixo também funciona
RUSTFLAGS="-Clinker=clang -Clink-args=-fuse-ld=wild" cargo test
  • Para usar o Wild como linker de código Rust em CI, consulte o wild-action
  • Informações de contribuição estão em CONTRIBUTING.md, e a visão geral de design em alto nível está em DESIGN.md
  • As conversas sobre o Wild acontecem no servidor Zulip
  • O blog de David tem muitos textos cobrindo vários aspectos do linker Wild
  • A licença pode ser escolhida entre Apache License 2.0 e MIT license

1 comentários

 
GN⁺ 2025-01-25
Opiniões do Hacker News
  • Depois que o mold foi relicenciado de AGPL para MIT (como parte do lançamento do mold 2.0), achei que a necessidade de criar outro linker rápido no mundo todo tinha diminuído bastante, então não esperava que um projeto desses aparecesse
    Além disso, em alguns casos ele já é 2x mais rápido que o mold, o que foi ainda mais inesperado; vou acompanhar como evolui e desejo sorte ao autor

    • O mold não pretende fazer linkagem incremental, então há um grande motivo para outro linker existir
      O linker da Microsoft tem linkagem incremental por padrão há décadas, então é meio constrangedor que no Linux ainda não exista um linker incremental pronto para produção
    • Por que a diferença entre AGPL e MIT importa em um linker?
    • Não sei como está o suporte a Windows do mold
      Pelas issues no GitHub, o autor inicialmente tentou dar suporte, depois transferiu o suporte a Windows para o linker sold; mas o sold foi arquivado recentemente, então fico confuso se no fim não há suporte a Windows ou se entendi mal o fluxo
    • Talvez eu esteja usando errado, mas, quando uso LTO, que provavelmente deveria usar, o mold não fica nada mais rápido
    • Espera aí, é possível relicenciar algo em GPL para MIT?
  • Dei uma olhada antes, mas ainda não sei se está pronto para uso em produção
    Pelo README, parece que não, então continuo usando mold
    Se você usa macOS, a Apple lançou um novo linker há cerca de 1 ou 2 anos, e por isso o autor do mold parou de trabalhar na versão para macOS
    Para usar com Rust, coloque isto no config.toml

    [target.aarch64-apple-darwin]

    rustflags = [

    "-C",

    "link-arg=-fuse-ld=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld",

    "-C",

    "link-arg=-ld_new",

    ]

    • O autor diz com bastante clareza que ainda não se deve usar em produção
    • Também não uso mold em produção; uso para desenvolvimento
    • Hoje em dia o novo linker já não é o padrão? Não tenho muita certeza se adicionar isso faz diferença
    • Dá para confirmar se esse local também está correto no Sequoia?
      Instalei as ferramentas de linha de comando e só tenho /usr/bin/ld e /usr/bin/ld-classic
  • Seria interessante se compiladores C/C++ eliminassem a etapa intermediária chamada linkagem e compilassem o programa inteiro como uma única unidade
    Se o compilador pudesse enxergar o programa inteiro desde o começo, o próprio LTO não seria necessário
    Para builds incrementais, ainda seria preciso armazenar alguns artefatos de build, mas não na forma de arquivos objeto; seria necessário ter metadados que conheçam a origem e as dependências do código gerado, para que apenas as partes corretas possam ser substituídas
    Hoje em dia, bibliotecas externas geralmente são linkadas dinamicamente, então não é preciso compilá-las a partir do código-fonte, e remover o linker também não causaria problemas para dependências privadas
    Se ainda assim não fosse suficiente, o compilador poderia aceitar arquivos objeto também, para lidar com casos legados ou especiais que precisam ser linkados estaticamente no binário

  • O interesse por linkers rápidos cresceu bastante por causa da adoção e popularidade do Rust
    Mesmo um binário Rust de linkagem estática e tamanho moderado pode levar minutos na etapa de linkagem de uma compilação em modo release (usando mold)
    Não é um problema exclusivo do Rust; é, em geral, o resultado da combinação de linkagem estática rigorosa, otimizações avançadas em tempo de link como LTO e BOLT fornecidas pelo LLVM, e as reclamações da comunidade Rust sobre tempo de compilação
    Por causa da forte relação do Rust com o LLVM — na prática, uma dependência —, ele acabou se tornando a linguagem em que a mágica de tempo de link do LLVM foi adotada de forma mais ampla; em C++ também é possível sofrer o mesmo problema, mas nesse caso é mais provável que a culpa seja atribuída à toolchain do que à linguagem
    Acompanhei o wild por um tempo por causa do potencial de se tornar um linker incremental com otimizações, mas, sinceramente, até que ele realmente consiga fazer linkagem incremental, não tenho nenhuma motivação para mexer nele

    • C++ pode compilar muito mais rápido que Rust porque alguns compiladores dão suporte a compilação incremental e linkagem incremental
      Além disso, o ecossistema C/C++ tem uma cultura de aceitar bibliotecas binárias, então muitas vezes você só precisa compilar a própria aplicação, sem recompilar o mundo inteiro toda vez que clona um repositório ou troca de branch de desenvolvimento
    • Resolvi esse problema com Wasm
      Se o shell externo da aplicação chamar a lógica de negócio em Wasm, basta recompilar só a lógica interna, e nem é preciso reiniciar o shell externo do app
  • 2008: gold, um novo linker criado para ser mais rápido que o GNU ld

    Por volta de 2015: lld, um linker substituto drop-in pelo menos 2x mais rápido que o gold

    2021: mold, um novo linker várias vezes mais rápido que o lld

    2025: o novo linker wild...

    • Isso raramente é mencionado, mas tudo isso foi possível ao custo de não implementar muitos recursos úteis usados por programas reais
    • Não sei se isso é uma observação negativa, positiva ou só um breve resumo histórico, mas é bem animador ver que ainda dá para extrair mais desempenho de linkers
    • O gold deve ser removido no binutils 2.44.0, então está oficialmente morto
    • No Windows, embora ainda esteja em estágio inicial, também existe The RAD Linker
  • Relacionado a isso, há um livro antigo mas bom: Linkers and Loaders, de John Levine
    É o último livro da lista abaixo

    https://www.johnlevine.com/books.phtml

    Li alguns anos atrás e achei bem interessante; é o livro de referência da área
    Como dá para ver no link, ele também escreveu outros livros populares sobre computação

  • Parece bastante promissor
    É escrito em Rust desde o início, e o objetivo é ser rápido e dar suporte a linkagem incremental
    Para usar com Rust, talvez também funcione usando o gcc como driver do linker
    No .cargo/config.toml do projeto:

    [target.x86_64-unknown-linux-gnu]

rustflags = ["-C", "link-arg=-fuse-ld=wild"]

É um desvio, mas por que o Rust precisa se encaixar no gcc ou no clang aqui? Falta algum recurso?

  • Infelizmente, o gcc não aceita um linker arbitrário pela flag -fuse-ld=
    Os linkers aceitos são apenas bfd, gold, lld e mold
    Dá para fazer o gcc chamar o wild como linker, mas atualmente é preciso criar um diretório contendo o linker wild, renomear o binário ou link simbólico para "ld" e então passar -B/path/to/directory/containing/wild para o gcc
    O motivo de o Rust não chamar o linker diretamente, mas chamar o linker via gcc ou clang, é que o compilador C sabe quais flags de linker são necessárias na plataforma atual para linkar com a libc e o runtime C
    Por exemplo, coisas como Scrt1.o, crti.o, crtbeginS.o, crtendS.o, crtn.o

  • Porque o compilador Rust gera bytecode IR, não código de máquina

  • Fiquei curioso: qual seria a razão teórica para isso ser mais rápido que o mold no caso não incremental?
    “Porque é Rust” é uma explicação ok para várias coisas, mas não explica uma vantagem de desempenho esperada
    Seria interessante se fosse “há oportunidades de paralelização de baixa dificuldade que o Rust facilita aproveitar”, mas isso não está explícito nem implícito

    • Não sei ao certo, principalmente porque não conheço bem a base de código do Mold
      Uma pista é que ouvi dizer que o Mold fica cerca de 10% mais rápido quando usa o alocador mais rápido mimalloc
      Testei mimalloc no Wild, mas não houve ganho de velocidade mensurável
      Isso sugere que o Mold usa mais o alocador do que o Wild, e no Wild claramente houve esforço para otimizar o número de alocações no heap
      No geral, vejo isso como uma diferença de decisões de design
      Quanto a como isso poderia se relacionar com Rust, tenho certeza de que, mesmo que o Wild fosse portado de Rust para C ou C++, o desempenho seria quase o mesmo
      Só que, em Rust, graças ao borrow checker, padrões de código aceitáveis podem virar armadilhas em C ou C++, dificultando a manutenção
      Quando eu programava em C++ no passado, eu escrevia de forma mais defensiva mesmo pagando pequenos custos de desempenho; em Rust, por saber que o compilador me dá cobertura, consigo escrever de forma muito mais ousada
  • Que coincidência
    Uma hora atrás comparei o desempenho de wild, mold e ld em um projeto C em que estou trabalhando
    São 23 mil linhas e 172 arquivos; com gcc+ld, levou cerca de 23,4 s de tempo de usuário, com gcc+mold 22,5 s, e com gcc+wild 21,8 s
    Então acho que, em um projeto bem estruturado, o tempo de linkedição talvez não seja um problema tão grande

    • Parece uma build do zero
      Nesse caso, a maior parte do tempo é gasta compilando código, não linkando
      A vantagem de um linker rápido é maior no desenvolvimento iterativo
      Ou seja, quando você faz uma pequena alteração no código, recompila e executa o resultado, o compilador normalmente quase não tem o que fazer, mas a linkedição ainda é feita do zero, então tende a virar o tempo dominante
    • O tempo de linkedição importa ao compilar algo como o Chrome, não projetos pequenos
    • Linkers rápidos geralmente são úteis para reduzir o ciclo de edição em cenários de compilação incremental
    • E o ld.lld?
  • “Este benchmark foi executado no notebook de David Lattimore (um System76 Lemur Pro de 2020), com 4 núcleos (8 threads) e 42 GB de RAM

    https://news.ycombinator.com/item?id=33330499

    Só para constar, não estou tentando dizer que o wild é inchado
    Se há um problema, ele está no software desenvolvido com isso e nos computadores das pessoas que usarão esse software

    • https://news.ycombinator.com/item?id=42896619

      “... a RAM é de 16 GB e não pode ser atualizada...”

    • Meio de brincadeira, mas imagino que quem programa em Rust já tenha 32 GB de RAM
      Pessoalmente, sacrificar literalmente todo o resto para atualizar meu notebook para 64 GB foi uma decisão quase excelente
      Digo quase porque eu deveria ter gasto dinheiro tanto em RAM quanto na tela, em vez de apostar tudo na RAM
      A única desvantagem é que limpar as abas abertas uma vez por semana virou uma tarefa que toma a noite inteira