GraalVM Native Image atende RISC-V

Jan 17 2023
Agora é possível usar GraalVM Native Image no RISC-V! Vou explicar aqui como compilar aplicações para RISC-V e a implementação. Por padrão, o Native Image usa o compilador Graal para produzir código de máquina, mas pode usar o compilador LLVM ao habilitar o back-end LLVM (documentação).

Agora é possível usar GraalVM Native Image no RISC-V! Vou explicar aqui como compilar aplicações para RISC-V e a implementação. Por padrão, o Native Image usa o compilador Graal para produzir código de máquina, mas pode usar o compilador LLVM ao habilitar o backend LLVM ( documentação ). Uma das principais razões para usar o LLVM é que ele oferece suporte a uma ampla variedade de arquiteturas, o que significa que não precisamos implementar o compilador Graal para elas, que é a etapa mais difícil ao adicionar suporte a uma nova arquitetura. Para demonstrar isso, adaptei o back-end Native Image LLVM para máquinas RISC-V no Linux com um GraalVM dev build .

RISC-V

RISC-V é uma arquitetura de conjunto de instruções (ISA) de código aberto criada em 2010. A principal diferença entre essa arquitetura e as atuais líderes de mercado é o fato de o RISC-V ser de código aberto. Embora seu primeiro uso pretendido fosse para pesquisa e educação, o interesse da indústria agora está crescendo.

Ele usa um ISA inteiro de base simples, que evita o ajuste excessivo de padrões de microarquitetura específicos e recursos superotimizados projetados para domínios específicos, reduzindo custos e melhorando o desempenho para tarefas mais diretas. Para usos mais complexos, extensões podem ser adicionadas ao ISA base, como suporte para multiplicação, pontos flutuantes, vetores e muito mais. Algumas dessas extensões são predefinidas, mas também podem ser personalizadas.

Demonstração do Micronauta

Para mostrar a diferença de performance entre rodar na JVM e Native Image, fizemos uma demonstração baseada em uma aplicação Micronaut, um pequeno servidor web, em um emulador RISC-V. As fontes de demonstração podem ser encontradas aqui .

Inicialização do servidor Micronaut usando um aplicativo compilado com Native Image:

__  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
Micronaut (v3.6.0)

16:52:29.965 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 305ms. Server Running: http://fedora-riscv:8080

Binários de compilação cruzada para RISC-V com o back-end LLVM

Considerando que a maioria das formas atuais de executar o Linux em uma máquina RISC-V são lentas, é aconselhável fazer a compilação cruzada dos binários para RISC-V. O Native Image é compatível com alguns pré-requisitos.

Cadeia de ferramentas RISC-V com zlib

A cadeia de ferramentas RISC-V deve ser fornecida ao Native Image. Pode ser encontrado aqui . As versões de gcce glibcprecisam ser as mesmas ou mais antigas que as usadas pela máquina RISC-V.

zlibestá incluído na cadeia de ferramentas RISC-V, mas deve ser compilado manualmente usando o arquivo riscv-gcc. A biblioteca produzida libz.adeve ser movida para path/to/built/toolchain/sysroot/usr/lib/.

CAPCaches e bibliotecas estáticas

Os CAPCaches são informações sobre os intrínsecos C na arquitetura de destino, bem como os valores dos recursos e sinalizadores da CPU. Eles podem ser baixados aqui .

Native Image precisa de bibliotecas estáticas Java e Graal. Eles podem ser baixados aqui .

Procedimento para a construção cruzada de um binário

O procedimento a seguir descreve como construir um binário a partir de um HelloWorld.javaarquivo simples.

O primeiro passo é atender aos pré-requisitos mencionados acima.

Em seguida, baixe uma compilação dev do GraalVM junto com a imagem nativa.

Por fim, execute os seguintes comandos:

$JAVA_HOME/bin/javac Helloworld.java
$JAVA_HOME/bin/native-image HelloWorld -H:CompilerBackend=llvm \
    -Dsvm.targetPlatformArch=riscv64 -H:CAPCacheDir=/path/to/capcache \
    -H:CCompilerPath=/path/to/riscv-gcc -H:CustomLD=/path/to/riscv-ld \
    -H:CLibraryPath=/path/to/static-libraries \
    --add-exports=jdk.internal.vm.ci/jdk.vm.ci.riscv64=org.graalvm.nativeimage.builder

  • -Dsvm.targetPlatformArch=riscv64permite especificar a arquitetura de destino.
  • -H:CompilerBackend=llvmespecifica que usamos o back-end LLVM.
  • -H:CAPCacheDir=/path/to/capcachefornece os CAPCaches pré-computados para a imagem nativa.
  • -H:CCompilerPath=/path/to/riscv-gccfornece gccda cadeia de ferramentas RISC-V para a imagem nativa.
  • -H:CustomLD=/path/to/riscv-ldfornece ldda cadeia de ferramentas RISC-V para a imagem nativa.
  • -H:CLibraryPath=/path/to/static-librariesfornece as bibliotecas estáticas para a imagem nativa.

Calcular CAPCaches e compilar bibliotecas estáticas manualmente

Para obter os CAPCaches ou as bibliotecas estáticas manualmente, uma solução é construir uma imagem nativa no RISC-V a partir do código-fonte. Isso pode ser feito usando os seguintes comandos.

git clone https://github.com/oracle/graal.git
git clone https://github.com/graalvm/mx.git
export PATH=$PWD/mx:$PATH
cd graal/substratevm
mx fetch-jdk
# Choose one of the jdk version and run the provided export command
export SKIP_LIBRARIES=true
mx build

Para calcular os CAPCaches, execute o seguinte comando.

$JAVA_HOME/bin/native-image HelloWorld -H:CompilerBackend=llvm \
    -H:+ExitAfterCAPCache -H:CAPCacheDir=/path/to/capcache -H:+NewCAPCache \
    -add-exports=jdk.internal.vm.ci/jdk.vm.ci.riscv64=org.graalvm.nativeimage.builder

Crie um binário RISC-V nativamente

Embora seja recomendado construir binários cruzados, ainda é possível construí-los em uma máquina RISC-V. O procedimento é muito semelhante ao cross-building. A cadeia de ferramentas não é mais necessária, pois será a nativa, e os CAPCaches e as bibliotecas estáticas serão fornecidos automaticamente. A última diferença está no comando final:

$JAVA_HOME/bin/native-image HelloWorld -H:CompilerBackend=llvm \
    --add-exports=jdk.internal.vm.ci/jdk.vm.ci.riscv64=org.graalvm.nativeimage.builder

Para adicionar o alvo RISC-V à Imagem Nativa, tivemos que implementar todas as mudanças descritas na documentação . No entanto, existem algumas outras alterações não mencionadas relacionadas ao RISC-V.

Implementação JVMCI

O compilador Graal depende da Java Virtual Machine Compiler Interface (JVMCI), o que significa que este subcomponente Java deve ser implementado para usar Native Image, pois depende do compilador Graal. Embora o back-end LLVM não use todo o compilador Graal, ele ainda usa seu front-end e, portanto, precisa de um subconjunto de JVMCI. A implementação deste subconjunto para RISC-V pode ser encontrada aqui .

Conclusão

O backend RISC-V LLVM suporta quase todos os recursos que os outros backends LLVM suportam!

Ainda há trabalho a fazer, como adicionar suporte para macOS e outros sistemas operacionais. Algumas otimizações específicas do alvo podem ser interessantes de investigar, já que o RISC-V fornece alguns registradores específicos que nem todas as outras arquiteturas possuem. Por exemplo, existe o registrador de thread ( x4).

Considerando que levou apenas cerca de seis meses para adicionar suporte RISC-V à imagem nativa usando o back-end LLVM, a ideia de adicionar suporte para outros alvos potenciais, como WebAssembly, é atraente. De fato, adicionar outras arquiteturas se tornará ainda mais acessível, pois o backend LLVM em breve emitirá a própria seção de dados Native Image, permitindo que ele pule a adição de informações sobre arquivos de objeto.

Congratulamo-nos com o seu feedback! Você pode compartilhá-lo via Slack , GitHub , Twitter ou Mastodon .