Теперь можно использовать собственный образ GraalVM на RISC-V! Здесь я объясню, как компилировать приложения для RISC-V и реализацию. По умолчанию Native Image использует компилятор Graal для создания машинного кода, но вместо этого он может использовать компилятор LLVM, включив бэкенд LLVM (документация). Одной из ключевых причин использования LLVM является то, что он поддерживает широкий спектр архитектур, а это означает, что нам не нужно реализовывать для них компилятор Graal, что является самым сложным шагом при добавлении поддержки новой архитектуры. Чтобы продемонстрировать это, я адаптировал бэкэнд Native Image LLVM для машин RISC-V под Linux с помощью GraalVM dev build.

RISC-V

RISC-V — архитектура набора инструкций (ISA) с открытым исходным кодом, созданная в 2010 году. Основное отличие этой архитектуры от нынешних лидеров рынка заключается в том, что RISC-V является открытым исходным кодом. Хотя изначально он предназначался для исследований и образования, интерес отрасли сейчас растет.

Он использует простую базовую целочисленную ISA, которая позволяет избежать чрезмерной подгонки конкретных микроархитектурных шаблонов и чрезмерно оптимизированных функций, разработанных для определенных областей, снижая затраты и повышая производительность для более простых задач. Для более сложных применений поверх базовой ISA могут быть добавлены расширения, такие как поддержка умножения, операций с плавающей запятой, векторов и т. д. Некоторые из этих расширений предопределены, но они также могут быть пользовательскими.

Демонстрация микронавтов

Чтобы показать разницу в производительности при работе на JVM и собственном образе, мы провели демонстрацию на основе приложения Micronaut, небольшого веб-сервера, на эмуляторе RISC-V. Исходники демо можно найти здесь.

Инициализация сервера Micronaut с помощью приложения, скомпилированного с 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

Мы наблюдали время запуска ~ 300 мс для приложения Micronaut, скомпилированного с Native Image. Для сравнения, время запуска при использовании Java составляет ~9300 мс. Таким образом, использование Native Image приводит к ускорению примерно в 31 раз!

Кросс-сборка двоичных файлов для RISC-V с серверной частью LLVM

Учитывая, что большинство текущих способов запуска Linux на машине RISC-V медленны, рекомендуется кросс-сборка двоичных файлов для RISC-V. Native Image поддерживает его с некоторыми предварительными требованиями.

Связка инструментов RISC-V с zlib

Цепочка инструментов RISC-V должна быть предоставлена ​​Native Image. Он может быть найден здесь". Версии gcc и glibc должны быть такими же или более ранними, чем версии, используемые машиной RISC-V.

zlib включен в цепочку инструментов RISC-V, но должен быть скомпилирован вручную с использованием созданного riscv-gcc. Выведенная библиотека libz.a должна быть перемещена в path/to/built/toolchain/sysroot/usr/lib/.

CAPCache и статические библиотеки

CAPCaches — это информация о встроенных функциях C в целевой архитектуре, а также значения функций и флагов ЦП. Их можно скачать здесь.

Native Image требует статических библиотек Java и Graal. Их можно скачать здесь.

Процедура перекрестной сборки двоичного файла

Следующая процедура описывает, как создать двоичный файл из простого файла HelloWorld.java.

Первым шагом является выполнение предварительных условий, упомянутых выше.

Затем загрузите dev-сборку GraalVM вместе с Native Image.

Наконец, выполните следующие команды:

$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=riscv64 позволяет указать целевую архитектуру.
  • -H:CompilerBackend=llvm указывает, что мы используем бэкенд LLVM.
  • -H:CAPCacheDir=/path/to/capcache предоставляет предварительно вычисленные CAPCache для собственного образа.
  • -H:CCompilerPath=/path/to/riscv-gcc предоставляет gcc из цепочки инструментов RISC-V для Native Image.
  • -H:CustomLD=/path/to/riscv-ld предоставляет ld из цепочки инструментов RISC-V для Native Image.
  • -H:CLibraryPath=/path/to/static-libraries предоставляет статические библиотеки для Native Image.

Подробнее о бэкэнде LLVM и других возможностях можно узнать в документации.

Вычисление CAPCaches и компиляция статических библиотек вручную

Чтобы получить CAPCache или статические библиотеки вручную, одним из решений является сборка Native Image на RISC-V из исходного кода. Это можно сделать с помощью следующих команд.

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

Тогда статические библиотеки будут в graal/sdk/latest_graalvm_home/lib и graal/sdk/latest_graalvm_home/lib/svm/clibraries/linux-riscv64/.

Чтобы вычислить CAPCache, выполните следующую команду.

$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

CAPCaches будут храниться в /path/to/capcache.

Нативное создание двоичного файла RISC-V

Несмотря на то, что рекомендуется выполнять перекрестную сборку двоичных файлов, их все же можно собрать на машине RISC-V. Процедура очень похожа на кроссбилдинг. Инструментальная цепочка больше не нужна, так как она будет нативной, а CAPCaches и статические библиотеки будут предоставляться автоматически. Последнее отличие заключается в финальной команде:

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

Реализация

Чтобы добавить цель RISC-V в Native Image, нам пришлось внести все изменения, описанные в документации. Однако есть некоторые другие неупомянутые изменения, связанные с RISC-V.

Реализация JVMCI

Компилятор Graal зависит от интерфейса компилятора виртуальной машины Java (JVMCI), а это означает, что этот подкомпонент Java должен быть реализован для использования Native Image, поскольку он зависит от компилятора Graal. Хотя серверная часть LLVM не использует весь компилятор Graal, она по-прежнему использует свой внешний интерфейс и, следовательно, нуждается в подмножестве JVMCI. Реализацию этого подмножества для RISC-V можно найти здесь.

Заключение

Бэкэнд RISC-V LLVM поддерживает почти все функции, поддерживаемые другими бэкендами LLVM!

Есть еще над чем поработать, например, добавить поддержку macOS и других операционных систем. Некоторые целевые оптимизации могут быть интересны для изучения, поскольку RISC-V предоставляет некоторые конкретные регистры, которые есть не во всех других архитектурах. Например, есть регистр потока (x4).

Учитывая, что на добавление поддержки RISC-V в Native Image с помощью бэкэнда LLVM ушло всего около шести месяцев, идея добавить поддержку других потенциальных целей, таких как WebAssembly, привлекательна. На самом деле, добавление других архитектур станет еще более доступным, поскольку бэкэнд LLVM вскоре сам выдаст раздел данных Native Image, что позволит ему пропустить добавление информации об объектных файлах.

Мы приветствуем ваши отзывы! Вы можете поделиться им через Slack, GitHub, Twitter или Mastodon.