Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 120 additions & 2 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,33 @@ jobs:
arch: "s390x"
library-arch: s390x-linux-gnu

# riscv32-ilp32 build
- os: "ubuntu-24.04"
cc: "riscv32-unknown-linux-gnu-gcc"
cxx: "riscv32-unknown-linux-gnu-g++"
cflags: "-O2"
otp: "28"
elixir_version: "1.17"
rebar3_version: "3.24.0"
cmake_opts_other: "-DAVM_WARNINGS_ARE_ERRORS=ON -DCMAKE_TOOLCHAIN_FILE=${RUNNER_TEMP}/riscv32_ilp32_toolchain.cmake"
compiler_pkgs: "qemu-user qemu-user-binfmt binfmt-support"
arch: "riscv32"
library-arch: riscv32-linux-gnu-ilp32

# riscv32-ilp32 build + jit
- os: "ubuntu-24.04"
cc: "riscv32-unknown-linux-gnu-gcc"
cxx: "riscv32-unknown-linux-gnu-g++"
cflags: "-O2"
otp: "28"
elixir_version: "1.17"
rebar3_version: "3.24.0"
cmake_opts_other: "-DAVM_DISABLE_JIT=OFF -DAVM_JIT_TARGET_ARCH=riscv32 -DCMAKE_TOOLCHAIN_FILE=${RUNNER_TEMP}/riscv32_ilp32_toolchain.cmake"
compiler_pkgs: "qemu-user qemu-user-binfmt binfmt-support"
arch: "riscv32"
library-arch: riscv32-linux-gnu-ilp32
jit_target_arch: "riscv32"

env:
ImageOS: ${{ matrix.container == 'ubuntu:20.04' && 'ubuntu20' || matrix.os == 'ubuntu-20.04' && 'ubuntu20' || matrix.os == 'ubuntu-22.04' && 'ubuntu22' || matrix.os == 'ubuntu-24.04' && 'ubuntu24' || 'ubuntu24' }}
CC: ${{ matrix.cc }}
Expand All @@ -386,7 +413,7 @@ jobs:
run: sudo dpkg --add-architecture i386

- name: "Setup cross compilation architecture"
if: matrix.library-arch != ''
if: matrix.library-arch != '' && matrix.library-arch != 'riscv32-linux-gnu-ilp32'
run: |
# Replace Azure mirrors with official Ubuntu repositories
sudo sed -i 's|azure\.||g' /etc/apt/sources.list
Expand Down Expand Up @@ -420,6 +447,97 @@ jobs:
set(MBEDTLS_LIBRARIES_DIR /usr/lib/${{ matrix.library-arch }})
EOF

- name: "Setup cross compilation architecture (riscv32)"
if: matrix.library-arch == 'riscv32-linux-gnu-ilp32'
run: |
sudo dpkg --add-architecture ${{ matrix.arch }}

# Download toolchain and libraries from release
gh release download riscv-toolchain-2025.10.18 \
-R pguyot/crossbuild-essential-riscv32 \
--pattern 'riscv32-gnu-toolchain-ilp32_2025.10.18_amd64.deb' \
--pattern 'libc6-ilp32_2.39-0ubuntu1_riscv32.deb' \
--pattern 'libc6-dev-ilp32_2.39-0ubuntu1_riscv32.deb' \
--pattern 'libc6-dbg-ilp32_2.39-0ubuntu1_riscv32.deb' \
--pattern 'zlib1g-ilp32_1.3.1-0ubuntu1_riscv32.deb' \
--pattern 'zlib1g-dev-ilp32_1.3.1-0ubuntu1_riscv32.deb' \
--pattern 'libmbedcrypto7-ilp32_2.28.8-0ubuntu1_riscv32.deb' \
--pattern 'libmbedtls-dev-ilp32_2.28.8-0ubuntu1_riscv32.deb' \
--pattern 'libmbedtls14-ilp32_2.28.8-0ubuntu1_riscv32.deb' \
--pattern 'libmbedx509-1-ilp32_2.28.8-0ubuntu1_riscv32.deb'

# Install the toolchain
sudo dpkg -i riscv32-gnu-toolchain-ilp32_2025.10.18_amd64.deb

# Add to PATH for all subsequent steps
echo "/opt/riscv32-ilp32/bin" >> $GITHUB_PATH

# Install the libs
sudo dpkg -i libc6-ilp32_2.39-0ubuntu1_riscv32.deb
sudo dpkg -i libc6-dev-ilp32_2.39-0ubuntu1_riscv32.deb
sudo dpkg -i libc6-dbg-ilp32_2.39-0ubuntu1_riscv32.deb

sudo dpkg -i zlib1g-ilp32_1.3.1-0ubuntu1_riscv32.deb
sudo dpkg -i zlib1g-dev-ilp32_1.3.1-0ubuntu1_riscv32.deb

# Install mbedtls runtime packages first (in dependency order)
sudo dpkg -i libmbedcrypto7-ilp32_2.28.8-0ubuntu1_riscv32.deb
sudo dpkg -i libmbedx509-1-ilp32_2.28.8-0ubuntu1_riscv32.deb
sudo dpkg -i libmbedtls14-ilp32_2.28.8-0ubuntu1_riscv32.deb
# Then install the dev package
sudo dpkg -i libmbedtls-dev-ilp32_2.28.8-0ubuntu1_riscv32.deb

sudo sed -i '/Types: deb/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources

cat > ${RUNNER_TEMP}/riscv32_ilp32_toolchain.cmake <<'EOF'
# Toolchain file for RISC-V32 ILP32 (RV32-IMAC) cross-compilation
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv32)
set(CMAKE_C_LIBRARY_ARCHITECTURE riscv32-linux-gnu-ilp32)

# Specify the cross compiler
set(CMAKE_C_COMPILER riscv32-unknown-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER riscv32-unknown-linux-gnu-g++)

# Specify the target architecture
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv32imac -mabi=ilp32" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv32imac -mabi=ilp32" CACHE STRING "" FORCE)

# Set up paths for cross-compiled libraries
set(ZLIB_LIBRARY /usr/lib/riscv32-linux-gnu-ilp32/libz.so CACHE FILEPATH "")
set(ZLIB_INCLUDE_DIR /usr/include/riscv32-linux-gnu CACHE PATH "")
set(ZLIB_FOUND TRUE CACHE BOOL "")

# MbedTLS configuration
set(MBEDTLS_ROOT_DIR /usr)
set(MBEDTLS_LIBRARIES_DIR /usr/lib/riscv32-linux-gnu-ilp32)

# Add cross-compilation include path to compiler flags
include_directories(SYSTEM /usr/include/riscv32-linux-gnu)

# Search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# Search for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
EOF

# Set up qemu-user binfmt to find libraries
sudo ln -s /opt/riscv32-ilp32/sysroot/lib/ld-linux-riscv32-ilp32.so.1 /lib/ld-linux-riscv32-ilp32.so.1
sudo mkdir -p /usr/gnemul
sudo ln -s /opt/riscv32-ilp32/sysroot /usr/gnemul/qemu-riscv32

# Copy cross-compiled libraries to sysroot for qemu-user
sudo cp /usr/lib/${{ matrix.library-arch }}/libz.so.1* /opt/riscv32-ilp32/sysroot/lib/
sudo cp /usr/lib/${{ matrix.library-arch }}/libmbedtls.so.14 /opt/riscv32-ilp32/sysroot/lib/
sudo cp /usr/lib/${{ matrix.library-arch }}/libmbedcrypto.so.7 /opt/riscv32-ilp32/sysroot/lib/
sudo cp /usr/lib/${{ matrix.library-arch }}/libmbedx509.so.1 /opt/riscv32-ilp32/sysroot/lib/

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: "APT update"
run: sudo apt update -y

Expand Down Expand Up @@ -630,7 +748,7 @@ jobs:
valgrind --error-exitcode=1 ./src/AtomVM tests/libs/jit/test_jit.avm

- name: "Test: test_jit.avm"
timeout-minutes: 20
timeout-minutes: 60
if: matrix.otp != '21' && matrix.otp != '22'
working-directory: build
run: |
Expand Down
49 changes: 38 additions & 11 deletions .github/workflows/pico-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,56 @@ jobs:
strategy:
matrix:
board: ["pico", "pico_w", "pico2"]
language: ["cpp"]
platform: [""]
jit: ["", "-DAVM_DISABLE_JIT=OFF"]
include:
- board: "pico2"
platform: "-DPICO_PLATFORM=rp2350-riscv"
jit: ""

- board: "pico2"
platform: "-DPICO_PLATFORM=rp2350-riscv"
jit: "-DAVM_DISABLE_JIT=OFF"

steps:
- name: Checkout repo
uses: actions/checkout@v4

- uses: erlef/setup-beam@v1
with:
otp-version: "28.1"
rebar3-version: "3.24.0"
hexpm-mirrors: |
https://builds.hex.pm
https://repo.hex.pm
https://cdn.jsdelivr.net/hex

- name: "apt update"
run: sudo apt update

- name: "Install deps"
run: |
sudo apt install -y \
cmake doxygen gperf ninja-build gcc-arm-none-eabi \
libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib \
erlang-base erlang-dev erlang-dialyzer erlang-eunit rebar3
libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib

- name: Install riscv32 toolchain
if: matrix.platform == '-DPICO_PLATFORM=rp2350-riscv'
run: |
sudo mkdir -p /opt/riscv32-toolchain
cd /opt/riscv32-toolchain
sudo wget -q https://github.com/raspberrypi/pico-sdk-tools/releases/download/v2.2.0-3/riscv-toolchain-15-x86_64-lin.tar.gz
sudo tar xzf riscv-toolchain-15-x86_64-lin.tar.gz
sudo rm riscv-toolchain-15-x86_64-lin.tar.gz
echo "/opt/riscv32-toolchain/bin" >> $GITHUB_PATH

- name: "Git config safe.directory for codeql"
run: git config --global --add safe.directory /__w/AtomVM/AtomVM

- name: "Initialize CodeQL"
uses: github/codeql-action/init@v3
with:
languages: ${{matrix.language}}
languages: "cpp"
build-mode: manual
queries: +./code-queries/term-to-non-term-func.ql,./code-queries/non-term-to-term-func.ql

Expand All @@ -74,7 +101,7 @@ jobs:
set -euo pipefail
mkdir build
cd build
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }}
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }} ${{ matrix.platform }} ${{ matrix.jit }}
ninja

- name: "Perform CodeQL Analysis"
Expand All @@ -97,7 +124,7 @@ jobs:
mkdir build.nosmp
cd build.nosmp
# TODO: fix all warnings and enable -DAVM_WARNINGS_ARE_ERRORS=ON
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }} -DAVM_DISABLE_SMP=1
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }} ${{ matrix.jit }} -DAVM_DISABLE_SMP=1
cmake --build . --target=rp2_tests

- name: Run tests with rp2040js
Expand All @@ -112,7 +139,7 @@ jobs:
npx tsx run-tests.ts ../build.nosmp/tests/rp2_tests.uf2 ../build.nosmp/tests/test_erl_sources/rp2_test_modules.uf2

- name: Build atomvmlib.uf2
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w'
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w' && matrix.platform == '' && matrix.jit == ''
shell: bash
run: |
set -euo pipefail
Expand All @@ -122,7 +149,7 @@ jobs:
make atomvmlib-${{ matrix.board }}.uf2

- name: Rename AtomVM and write sha256sum
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/') && matrix.platform == '' && matrix.jit == ''
shell: bash
run: |
pushd src/platforms/rp2/build
Expand All @@ -137,7 +164,7 @@ jobs:
popd

- name: Rename atomvmlib and write sha256sum
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w'
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w' && matrix.platform == '' && matrix.jit == ''
shell: bash
run: |
pushd build/libs
Expand All @@ -148,7 +175,7 @@ jobs:

- name: Release (Pico & Pico2)
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w'
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w' && matrix.platform == '' && matrix.jit == ''
with:
draft: true
fail_on_unmatched_files: true
Expand All @@ -160,7 +187,7 @@ jobs:

- name: Release (PicoW)
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') && matrix.board == 'pico_w'
if: startsWith(github.ref, 'refs/tags/') && matrix.board == 'pico_w' && matrix.platform == '' && matrix.jit == ''
with:
draft: true
fail_on_unmatched_files: true
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ if (NOT AVM_DISABLE_JIT AND NOT DEFINED AVM_JIT_TARGET_ARCH)
endif()
endif()

set(AVM_PRECOMPILED_TARGETS "x86_64;aarch64;armv6m;armv6m+float32" CACHE STRING "Targets to precompile code to if AVM_DISABLE_JIT is OFF or AVM_ENABLE_PRECOMPILED is ON")
set(AVM_PRECOMPILED_TARGETS "x86_64;aarch64;armv6m;armv6m+float32;riscv32" CACHE STRING "Targets to precompile code to if AVM_DISABLE_JIT is OFF or AVM_ENABLE_PRECOMPILED is ON")

if((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") OR
(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") OR
Expand Down
13 changes: 10 additions & 3 deletions doc/src/atomvm-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Following BEAM, there are two flavors of the emulator: jit and emu, but eventual
- Native: the VM only runs native code and all code must be precompiled on the desktop using the JIT compiler (which effectively is a AOT or Ahead-of-Time compiler). In this mode, it is not necessary to bundle the jit compiler on the embedded target.
- Hybrid: the VM can run native code as well as emulated BEAM code and some code is precompiled on the desktop.

JIT is available on some platforms (currently only x86_64, aarch64 and armv6m) and compiles Erlang bytecode at runtime. Erlang bytecode is never interpreted. EMU is available on all platforms and Erlang bytecode is interpreted.
JIT is available on some platforms (currently x86_64, aarch64, armv6m and riscv32) and compiles Erlang bytecode at runtime. Erlang bytecode is never interpreted. EMU is available on all platforms and Erlang bytecode is interpreted.

Modules can include precompiled code in a dedicated beam chunk with name 'avmN'. The chunk can contain native code for several architectures, however it may only contain native code for a given version of the native interface. Current version is 1. This native code is executed by the jit-flavor of the emulator as well as the emu flavor if execution of precompiled is enabled.

Expand All @@ -154,9 +154,16 @@ The JIT compiler is written in Erlang and is therefore precompiled. When a proce

JIT compiler is composed of two main interfaces : backend and stream.

A backend implementation is required for each architecture. The backend is called by jit module as it translates bytecodes to machine code. The current implementations are `jit_x86_64` and `jit_aarch64` which are suitable for systems with System V X86 64 ABI or AArch64 ABI.
A backend implementation is required for each architecture. The backend is called by jit module as it translates bytecodes to machine code. The current implementations are :
- `jit_x86_64` for System V X86 64 ABI
- `jit_aarch64` for AArch64 ABI
- `jit_armv6m` for AArch32 ABI
- `jit_riscv32` for rv32imc ilp32 ABI.

A stream implementation is responsible for streaming the machine code, especially in the context of low memory. Two implementations currently exist: `jit_stream_binary` that streams assembly code to an Erlang binary, suitable for tests and precompilation on the desktop, and `jit_stream_mmap` that streams assembly code in an `mmap(2)` allocated page, suitable for JIT compilation on Unix.
A stream implementation is responsible for streaming the machine code, especially in the context of low memory. Three implementations currently exist:
- `jit_stream_binary` that streams assembly code to an Erlang binary, suitable for tests and precompilation on the desktop
- `jit_stream_mmap` that streams assembly code in an `mmap(2)` allocated page, suitable for JIT compilation on Unix
- `jit_stream_flash` available on Pico that allows for embedded JIT.

### Embedded JIT and Native

Expand Down
1 change: 1 addition & 0 deletions libs/jit/include/jit.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
-define(JIT_ARCH_X86_64, 1).
-define(JIT_ARCH_AARCH64, 2).
-define(JIT_ARCH_ARMV6M, 3).
-define(JIT_ARCH_RISCV32, 4).

-define(JIT_VARIANT_PIC, 1).
-define(JIT_VARIANT_FLOAT32, 2).
Expand Down
2 changes: 2 additions & 0 deletions libs/jit/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ set(ERLANG_MODULES
jit_aarch64_asm
jit_armv6m
jit_armv6m_asm
jit_riscv32
jit_riscv32_asm
jit_x86_64
jit_x86_64_asm
)
Expand Down
1 change: 1 addition & 0 deletions libs/jit/src/jit_precompile.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ compile(Target, Dir, Path) ->
"x86_64" -> ?JIT_ARCH_X86_64;
"aarch64" -> ?JIT_ARCH_AARCH64;
"armv6m" -> ?JIT_ARCH_ARMV6M;
"riscv32" -> ?JIT_ARCH_RISCV32;
_ -> error({unsupported_target, Target})
end,

Expand Down
Loading
Loading