Fix audio race condition with heap allocation #4256
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: [push, pull_request] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| detect-code-related-file-changes: | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| has_code_related_changes: ${{ steps.set_has_code_related_changes.outputs.has_code_related_changes }} | |
| steps: | |
| - name: Check out the repo | |
| uses: actions/checkout@v6 | |
| - name: Test changed files | |
| id: changed-files | |
| uses: tj-actions/changed-files@v47 | |
| with: | |
| files: | | |
| .ci/** | |
| .github/workflows/** | |
| build/** | |
| configs/** | |
| mk/** | |
| scripts/** | |
| src/** | |
| tests/** | |
| tools/** | |
| .clang-format | |
| Dockerfile | |
| Makefile | |
| # Build system configuration | |
| mk/kconfig.mk | |
| mk/toolchain.mk | |
| tools/detect-env.py | |
| - name: Set has_code_related_changes | |
| id: set_has_code_related_changes | |
| run: | | |
| if [[ ${{ steps.changed-files.outputs.any_changed }} == true ]]; then | |
| echo "has_code_related_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_code_related_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| host-x64: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| compiler: [gcc, clang] | |
| runs-on: ubuntu-24.04 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| - name: Read LLVM Version | |
| id: llvm-version | |
| run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT | |
| - name: Cache LLVM | |
| id: cache-llvm | |
| uses: actions/cache@v5 | |
| with: | |
| path: /usr/lib/llvm-${{ steps.llvm-version.outputs.version }} | |
| key: ${{ runner.os }}-${{ runner.arch }}-llvm-${{ steps.llvm-version.outputs.version }} | |
| - name: Cache RISC-V Toolchain | |
| id: cache-toolchain | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ github.workspace }}/toolchain | |
| key: ${{ runner.os }}-${{ runner.arch }}-riscv-toolchain-${{ hashFiles('.ci/riscv-toolchain-install.sh') }} | |
| - name: Install dependencies | |
| run: | | |
| set +e # Disable errexit | |
| # Configure apt to tolerate mirror sync failures | |
| sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF' | |
| APT::Update::Error-Mode "any"; | |
| Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false"; | |
| Acquire::Retries "3"; | |
| Acquire::Check-Valid-Until "false"; | |
| EOF | |
| # Update with error tolerance | |
| sudo apt-get update -q=2 2>&1 | |
| UPDATE_EXIT=$? | |
| if [ $UPDATE_EXIT -ne 0 ]; then | |
| echo "WARNING: apt update exited with code $UPDATE_EXIT, continuing..." | |
| fi | |
| sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full | |
| set -e # Re-enable errexit | |
| shell: bash | |
| - name: Install RISC-V Toolchain | |
| if: steps.cache-toolchain.outputs.cache-hit != 'true' | |
| run: | | |
| .ci/riscv-toolchain-install.sh | |
| - name: Setup RISC-V Toolchain PATH | |
| run: echo "${{ github.workspace }}/toolchain/bin" >> $GITHUB_PATH | |
| - name: Install LLVM | |
| if: steps.cache-llvm.outputs.cache-hit != 'true' | |
| run: | | |
| .ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x ./llvm.sh | |
| sudo ./llvm.sh ${{ steps.llvm-version.outputs.version }} | |
| - name: Setup LLVM PATH | |
| run: echo "/usr/lib/llvm-${{ steps.llvm-version.outputs.version }}/bin" >> $GITHUB_PATH | |
| - name: Install compiler | |
| id: install_cc | |
| uses: rlalik/setup-cpp-compiler@master | |
| with: | |
| compiler: ${{ matrix.compiler }} | |
| - name: Setup emsdk | |
| uses: mymindstorm/setup-emsdk@v14 | |
| with: | |
| version: 3.1.51 | |
| actions-cache-folder: 'emsdk-cache' | |
| - name: Set parallel jobs variable | |
| run: | | |
| echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV" | |
| echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \ | |
| sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \ | |
| . \${TMP_FILE}; \ | |
| .ci/boot-linux.sh; \ | |
| EXIT_CODE=\$?; \ | |
| sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \ | |
| BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \ | |
| exit \${EXIT_CODE};" >> "$GITHUB_ENV" | |
| - name: Cache build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: build/ | |
| key: rv32emu-artifacts-${{ runner.os }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }} | |
| restore-keys: | | |
| rv32emu-artifacts-${{ runner.os }}- | |
| - name: Fetch artifacts | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| . .ci/common.sh | |
| fetch_artifact ELF | |
| fetch_artifact Linux-Image ENABLE_SYSTEM=1 | |
| rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination | |
| fetch_artifact sail ENABLE_ARCH_TEST=1 | |
| # get from rv32emu-prebuilt | |
| .ci/fetch.sh -o build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" | |
| unzip -o -d build/ build/shareware_doom_iwad.zip | |
| # emcc builds run on x64 only - WebAssembly output is platform-independent | |
| # This validates Emscripten compatibility without redundant builds on macOS | |
| # Note: wasm_defconfig sets CONFIG_BUILD_WASM=y which auto-selects CC=emcc | |
| - name: default build using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make wasm_defconfig | |
| make $PARALLEL | |
| - name: default build for system emulation using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make wasm_defconfig | |
| make ENABLE_SYSTEM=1 ENABLE_GOLDFISH_RTC=1 $PARALLEL | |
| make distclean | |
| - name: Build with various optimization levels | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Test boundary cases: -O0 (unoptimized), -O2 (standard), -Ofast (aggressive) | |
| for opt_level in -O0 -O2 -Ofast; do | |
| echo "Building with OPT_LEVEL=$opt_level" | |
| if ! (make distclean && make defconfig && make OPT_LEVEL=$opt_level $PARALLEL); then | |
| echo "ERROR: Build failed with OPT_LEVEL=$opt_level" | |
| exit 1 | |
| fi | |
| done | |
| - name: Build system emulation with various optimization levels | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Test boundary cases: -O0 (unoptimized), -O2 (standard), -Ofast (aggressive) | |
| for opt_level in -O0 -O2 -Ofast; do | |
| echo "Building system emulation with OPT_LEVEL=$opt_level" | |
| if ! (make distclean && make system_defconfig && make OPT_LEVEL=$opt_level $PARALLEL); then | |
| echo "ERROR: System emulation build failed with OPT_LEVEL=$opt_level" | |
| exit 1 | |
| fi | |
| done | |
| - name: check + tests | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make cleanconfig | |
| make defconfig | |
| make check $PARALLEL | |
| make tests $PARALLEL | |
| make misalign $PARALLEL | |
| make tool $PARALLEL | |
| - name: core extension disable tests (interpreter + JIT) | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Core extensions + bit manipulation + CSR/fence + performance features | |
| # Tests both interpreter and JIT modes (12 × 2 = 24 builds) | |
| # Consolidated from 25 separate builds (13 interpreter + 12 JIT) | |
| # Use cleanconfig instead of distclean to preserve artifacts across iterations | |
| for ext in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \ | |
| ENABLE_Zicsr ENABLE_Zifencei \ | |
| ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do | |
| echo "Testing ${ext}=0 (interpreter)" | |
| if ! (make cleanconfig && make defconfig && make ${ext}=0 check $PARALLEL); then | |
| echo "ERROR: Interpreter test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| echo "Testing ${ext}=0 (JIT)" | |
| if ! (make cleanconfig && make jit_defconfig && make ${ext}=0 check $PARALLEL); then | |
| echo "ERROR: JIT test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: SDL disable test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| # SDL is graphics subsystem, interpreter-only test sufficient | |
| make distclean && make defconfig && make ENABLE_SDL=0 check $PARALLEL | |
| - name: misalignment test in block emulation | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make -C tests/system/alignment/ | |
| make distclean && make system_defconfig && make ENABLE_ELF_LOADER=1 ENABLE_EXT_C=0 misalign-in-blk-emu $PARALLEL | |
| - name: MMU test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make -C tests/system/mmu/ | |
| make distclean && make system_defconfig && make ENABLE_ELF_LOADER=1 mmu-test $PARALLEL | |
| - name: gdbstub test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make defconfig && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL | |
| - name: JIT base test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| # Base JIT test (extension disable tests handled in consolidated step above) | |
| make distclean && make jit_defconfig && make check $PARALLEL | |
| - name: undefined behavior test | |
| if: success() || failure() | |
| run: | | |
| make distclean && make defconfig && make ENABLE_UBSAN=1 check $PARALLEL | |
| make distclean && make jit_defconfig && make ENABLE_UBSAN=1 check $PARALLEL | |
| # Boot tests moved to boot-tests-x64 job for parallel execution | |
| - name: Architecture test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| . .ci/common.sh | |
| export LATEST_RELEASE=$(fetch_latest_release sail) | |
| .ci/riscv-tests.sh | |
| # Parallel boot tests for x64 - runs interpreter, JIT, and T2C boot tests concurrently | |
| # T2C runs only on clang since it uses LLVM | |
| boot-tests-x64: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 30 # T2C boot ~18min + build ~3min + setup ~3min | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - compiler: gcc | |
| boot_type: interpreter | |
| defconfig: system_defconfig | |
| make_flags: "" | |
| timeout: 60 | |
| - compiler: gcc | |
| boot_type: jit | |
| defconfig: system_defconfig | |
| make_flags: "ENABLE_JIT=1 ENABLE_MOP_FUSION=0" | |
| timeout: 90 | |
| - compiler: clang | |
| boot_type: interpreter | |
| defconfig: system_defconfig | |
| make_flags: "" | |
| timeout: 60 | |
| - compiler: clang | |
| boot_type: jit | |
| defconfig: system_defconfig | |
| make_flags: "ENABLE_JIT=1 ENABLE_MOP_FUSION=0" | |
| timeout: 90 | |
| - compiler: clang | |
| boot_type: t2c | |
| defconfig: system_jit_defconfig | |
| make_flags: "ENABLE_MOP_FUSION=0" | |
| timeout: 120 | |
| runs-on: ubuntu-24.04 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| - name: Read LLVM Version | |
| if: matrix.compiler == 'clang' | |
| id: llvm-version | |
| run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT | |
| # LLVM needed for all clang builds (llvm-ar required for LTO) | |
| - name: Cache LLVM | |
| if: matrix.compiler == 'clang' | |
| id: cache-llvm | |
| uses: actions/cache@v5 | |
| with: | |
| path: /usr/lib/llvm-${{ steps.llvm-version.outputs.version }} | |
| key: ${{ runner.os }}-${{ runner.arch }}-llvm-${{ steps.llvm-version.outputs.version }} | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update -q=2 || true | |
| sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full e2fsprogs | |
| - name: Install LLVM | |
| if: matrix.compiler == 'clang' && steps.cache-llvm.outputs.cache-hit != 'true' | |
| run: | | |
| .ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x ./llvm.sh | |
| sudo ./llvm.sh ${{ steps.llvm-version.outputs.version }} | |
| - name: Setup LLVM PATH | |
| if: matrix.compiler == 'clang' | |
| run: echo "/usr/lib/llvm-${{ steps.llvm-version.outputs.version }}/bin" >> $GITHUB_PATH | |
| - name: Install compiler | |
| id: install_cc | |
| uses: rlalik/setup-cpp-compiler@master | |
| with: | |
| compiler: ${{ matrix.compiler }} | |
| - name: Set parallel jobs variable | |
| run: | | |
| echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV" | |
| echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp \"$RUNNER_TEMP/tmpfile.XXXXXX\"); \ | |
| sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \ | |
| . \${TMP_FILE}; \ | |
| .ci/boot-linux.sh; \ | |
| EXIT_CODE=\$?; \ | |
| sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \ | |
| BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \ | |
| exit \${EXIT_CODE};" >> "$GITHUB_ENV" | |
| # Cache key includes matrix variables to prevent cross-contamination between parallel jobs | |
| - name: Cache build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: build/ | |
| key: rv32emu-boot-${{ runner.os }}-${{ matrix.compiler }}-${{ matrix.boot_type }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }} | |
| restore-keys: | | |
| rv32emu-boot-${{ runner.os }}-${{ matrix.compiler }}-${{ matrix.boot_type }}- | |
| rv32emu-artifacts-${{ runner.os }}- | |
| - name: Fetch Linux artifacts | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| . .ci/common.sh | |
| fetch_artifact Linux-Image ENABLE_SYSTEM=1 | |
| rm -f build/.config | |
| - name: boot Linux kernel test (${{ matrix.boot_type }}) | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| BOOT_TIMEOUT: ${{ matrix.timeout }} | |
| run: | | |
| # For T2C, verify LLVM 18 detection | |
| if [ "${{ matrix.boot_type }}" = "t2c" ]; then | |
| rm -f build/t2c.o | |
| fi | |
| make distclean && make ${{ matrix.defconfig }} && make INITRD_SIZE=32 ${{ matrix.make_flags }} $PARALLEL && make artifact $PARALLEL | |
| if [ "${{ matrix.boot_type }}" = "t2c" ]; then | |
| test -f build/t2c.o || { echo "ERROR: T2C disabled (build/t2c.o missing) - LLVM 18 detection failed"; exit 1; } | |
| fi | |
| bash -c "${BOOT_LINUX_TEST}" | |
| # Native AArch64 on GitHub ARM runners - fast, no QEMU overhead | |
| host-arm64: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 45 | |
| runs-on: ubuntu-24.04-arm | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| - name: Set LLVM version | |
| run: echo "LLVM_VERSION=$(cat .ci/llvm-version)" >> $GITHUB_ENV | |
| - name: Install dependencies | |
| run: | | |
| # Configure apt to tolerate ARM mirror sync failures | |
| sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF' | |
| APT::Update::Error-Mode "any"; | |
| Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false"; | |
| Acquire::Retries "3"; | |
| Acquire::Check-Valid-Until "false"; | |
| EOF | |
| # Update with error tolerance | |
| sudo apt-get update -q=2 || { | |
| echo "WARNING: apt update failed, continuing with cached indexes..." | |
| } | |
| # Install packages with retry for critical ones | |
| sudo apt-get install -q=2 -y make curl wget libsdl2-dev libsdl2-mixer-dev \ | |
| lsb-release software-properties-common gnupg bc device-tree-compiler expect p7zip-full e2fsprogs || { | |
| echo "WARNING: Some packages failed, retrying critical ones..." | |
| sudo apt-get install -q=2 -y --no-download make curl wget || true | |
| } | |
| - name: Install LLVM | |
| run: | | |
| .ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x ./llvm.sh | |
| sudo ./llvm.sh $LLVM_VERSION | |
| - name: Setup LLVM PATH | |
| run: echo "/usr/lib/llvm-${LLVM_VERSION}/bin" >> $GITHUB_PATH | |
| - name: Set parallel jobs variable | |
| run: | | |
| echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV" | |
| echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp \"$RUNNER_TEMP/tmpfile.XXXXXX\"); \ | |
| sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \ | |
| . \${TMP_FILE}; \ | |
| .ci/boot-linux.sh; \ | |
| EXIT_CODE=\$?; \ | |
| sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \ | |
| BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \ | |
| exit \${EXIT_CODE};" >> "$GITHUB_ENV" | |
| - name: Fetch artifacts | |
| env: | |
| CC: clang-${{ env.LLVM_VERSION }} | |
| run: | | |
| . .ci/common.sh | |
| fetch_artifact ELF | |
| fetch_artifact Linux-Image ENABLE_SYSTEM=1 | |
| rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination | |
| # FIXME: gcc build fails on AArch64/Linux hosts | |
| - name: check + tests | |
| env: | |
| CC: clang-${{ env.LLVM_VERSION }} | |
| run: | | |
| make cleanconfig | |
| make defconfig | |
| make check $PARALLEL | |
| - name: JIT tests | |
| env: | |
| CC: clang-${{ env.LLVM_VERSION }} | |
| run: | | |
| set -euo pipefail | |
| make distclean && make jit_defconfig && make check $PARALLEL | |
| for ext in ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C; do | |
| echo "JIT test with ${ext}=0" | |
| make ENABLE_JIT=1 clean && make ${ext}=0 ENABLE_JIT=1 check $PARALLEL | |
| done | |
| - name: boot Linux kernel test (T2C) | |
| if: success() | |
| env: | |
| CC: clang-${{ env.LLVM_VERSION }} | |
| BOOT_TIMEOUT: 120 # T2C with O1 optimization (via defconfig) is faster than O3 | |
| run: | | |
| # Remove stale t2c.o before build to ensure fresh detection | |
| rm -f build/t2c.o | |
| # system_jit_defconfig uses T2C_OPT_LEVEL=1 for ~50% faster LLVM compilation | |
| make distclean && make system_jit_defconfig && make INITRD_SIZE=32 ENABLE_MOP_FUSION=0 $PARALLEL && make artifact $PARALLEL | |
| # Verify T2C is actually compiled (build/t2c.o only exists when LLVM 18 detection succeeds) | |
| test -f build/t2c.o || { echo "ERROR: T2C disabled (build/t2c.o missing) - LLVM 18 detection failed"; exit 1; } | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make clean | |
| macOS-arm64: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| compiler: [gcc-15, clang] | |
| runs-on: macos-latest # M1 chip | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| - name: Read LLVM Version | |
| id: llvm-version | |
| run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT | |
| - name: Cache RISC-V Toolchain | |
| id: cache-toolchain | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ github.workspace }}/toolchain | |
| key: ${{ runner.os }}-${{ runner.arch }}-riscv-toolchain-${{ hashFiles('.ci/riscv-toolchain-install.sh') }} | |
| - name: Cache Homebrew packages | |
| id: cache-brew | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| /opt/homebrew/Cellar/llvm@${{ steps.llvm-version.outputs.version }} | |
| /opt/homebrew/Cellar/sdl2 | |
| /opt/homebrew/Cellar/dtc | |
| key: ${{ runner.os }}-${{ runner.arch }}-brew-llvm${{ steps.llvm-version.outputs.version }}-${{ hashFiles('.github/workflows/main.yml') }} | |
| - name: Install dependencies | |
| run: | | |
| brew install make dtc expect sdl2 bc e2fsprogs p7zip llvm@${{ steps.llvm-version.outputs.version }} dcfldd | |
| brew install sdl2_mixer || echo "Warning: sdl2_mixer installation failed, continuing without SDL_MIXER support" | |
| # Ensure symlinks exist after cache restore (cached Cellar may lack /opt/homebrew/bin links) | |
| brew link dtc --overwrite 2>/dev/null || true | |
| command -v dtc || { echo "ERROR: dtc not found in PATH after install"; exit 1; } | |
| - name: Install RISC-V Toolchain | |
| if: steps.cache-toolchain.outputs.cache-hit != 'true' | |
| run: | | |
| .ci/riscv-toolchain-install.sh | |
| - name: Setup toolchain paths | |
| run: | | |
| echo "${{ github.workspace }}/toolchain/bin" >> $GITHUB_PATH | |
| echo "$(brew --prefix llvm@${{ steps.llvm-version.outputs.version }})/bin" >> $GITHUB_PATH | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install compiler | |
| id: install_cc | |
| uses: rlalik/setup-cpp-compiler@master | |
| with: | |
| compiler: ${{ matrix.compiler }} | |
| # NOTE: emsdk removed - emcc builds run on host-x64 only (WebAssembly is platform-independent) | |
| - name: Set parallel jobs variable | |
| run: | | |
| echo "PARALLEL=-j$(sysctl -n hw.logicalcpu)" >> "$GITHUB_ENV" | |
| echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \ | |
| sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \ | |
| . \${TMP_FILE}; \ | |
| .ci/boot-linux.sh; \ | |
| EXIT_CODE=\$?; \ | |
| sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} .ci/boot-linux-prepare.sh cleanup; \ | |
| exit \${EXIT_CODE};" >> "$GITHUB_ENV" | |
| - name: Symlink gcc-15 due to the default /usr/local/bin/gcc links to system's clang | |
| run: | | |
| ln -s /opt/homebrew/opt/gcc/bin/gcc-15 /usr/local/bin/gcc-15 | |
| - name: Cache build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: build/ | |
| key: rv32emu-artifacts-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }} | |
| restore-keys: | | |
| rv32emu-artifacts-${{ runner.os }}-${{ runner.arch }}- | |
| - name: fetch artifact first to reduce HTTP requests | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| . .ci/common.sh | |
| fetch_artifact ELF | |
| fetch_artifact Linux-Image ENABLE_SYSTEM=1 | |
| rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination | |
| fetch_artifact sail ENABLE_ARCH_TEST=1 | |
| # get from rv32emu-prebuilt | |
| .ci/fetch.sh -o build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" | |
| unzip -o -d build/ build/shareware_doom_iwad.zip | |
| # NOTE: emcc builds removed - they run on host-x64 only since WebAssembly output is platform-independent | |
| - name: check + tests | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make cleanconfig | |
| make defconfig | |
| make check $PARALLEL | |
| make tests $PARALLEL | |
| make misalign $PARALLEL | |
| make tool $PARALLEL | |
| - name: core extension disable tests (interpreter + JIT) | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Core extensions + bit manipulation + CSR/fence + performance features | |
| # Tests both interpreter and JIT modes (12 × 2 = 24 builds) | |
| # Consolidated from 25 separate builds (13 interpreter + 12 JIT) | |
| # Use cleanconfig instead of distclean to preserve artifacts across iterations | |
| for ext in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \ | |
| ENABLE_Zicsr ENABLE_Zifencei \ | |
| ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do | |
| echo "Testing ${ext}=0 (interpreter)" | |
| if ! (make cleanconfig && make defconfig && make ${ext}=0 check $PARALLEL); then | |
| echo "ERROR: Interpreter test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| echo "Testing ${ext}=0 (JIT)" | |
| if ! (make cleanconfig && make jit_defconfig && make ${ext}=0 check $PARALLEL); then | |
| echo "ERROR: JIT test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: SDL disable test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| # SDL is graphics subsystem, interpreter-only test sufficient | |
| make distclean && make defconfig && make ENABLE_SDL=0 check $PARALLEL | |
| - name: gdbstub test, need RV32 toolchain | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make defconfig && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL | |
| - name: JIT base test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| # Base JIT test (extension disable tests handled in consolidated step above) | |
| make distclean && make jit_defconfig && make check $PARALLEL | |
| - name: undefined behavior test | |
| if: (success() || failure()) && steps.install_cc.outputs.cc == 'clang' # gcc on macOS/arm64 does not support sanitizers | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make defconfig && make ENABLE_UBSAN=1 check $PARALLEL | |
| make distclean && make jit_defconfig && make ENABLE_UBSAN=1 check $PARALLEL | |
| - name: boot Linux kernel test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make system_defconfig && make INITRD_SIZE=32 $PARALLEL && \ | |
| make artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make clean | |
| - name: boot Linux kernel test (JIT) | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| BOOT_TIMEOUT: 90 # JIT compilation adds significant overhead | |
| run: | | |
| make distclean && make system_defconfig && make INITRD_SIZE=32 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 $PARALLEL && make artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make clean | |
| - name: Architecture test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| . .ci/common.sh | |
| export LATEST_RELEASE=$(fetch_latest_release sail) | |
| python3 -m venv venv | |
| . venv/bin/activate | |
| .ci/riscv-tests.sh | |
| coding-style: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 15 | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: coding convention | |
| run: | | |
| wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.gpg >/dev/null | |
| echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list | |
| sudo apt-get update -q=2 | |
| sudo apt-get install -q=2 clang-format-20 shfmt python3-pip | |
| pip3 install black==25.1.0 | |
| curl -LSfs https://go.mskelton.dev/dtsfmt/install | sh -s -- -y | |
| .ci/check-newline.sh | |
| .ci/check-format.sh | |
| shell: bash | |
| static-analysis: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 45 | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| # LLVM static analysis | |
| - name: set up scan-build | |
| run: | | |
| set +e # Disable errexit | |
| # Configure apt to tolerate mirror sync failures | |
| sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF' | |
| APT::Update::Error-Mode "any"; | |
| Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false"; | |
| Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false"; | |
| Acquire::Retries "3"; | |
| Acquire::Check-Valid-Until "false"; | |
| EOF | |
| # Update with error tolerance | |
| sudo apt-get update -q=2 2>&1 | |
| UPDATE_EXIT=$? | |
| if [ $UPDATE_EXIT -ne 0 ]; then | |
| echo "WARNING: apt update exited with code $UPDATE_EXIT, continuing..." | |
| fi | |
| sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev | |
| LLVM_VERSION=$(cat .ci/llvm-version) | |
| .ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x ./llvm.sh | |
| sudo ./llvm.sh $LLVM_VERSION | |
| sudo apt-get install -q=2 clang-${LLVM_VERSION} clang-tools-${LLVM_VERSION} | |
| set -e # Re-enable errexit | |
| shell: bash | |
| - name: run scan-build without JIT | |
| env: | |
| LATEST_RELEASE: dummy | |
| run: | | |
| LLVM_VERSION=$(cat .ci/llvm-version) | |
| make distclean && make defconfig && scan-build-${LLVM_VERSION} -v -o ~/scan-build --status-bugs --use-cc=clang-${LLVM_VERSION} --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=0 | |
| - name: run scan-build with JIT | |
| env: | |
| LATEST_RELEASE: dummy | |
| run: | | |
| LLVM_VERSION=$(cat .ci/llvm-version) | |
| make distclean && make jit_defconfig && scan-build-${LLVM_VERSION} -v -o ~/scan-build --status-bugs --use-cc=clang-${LLVM_VERSION} --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=1 | |
| # https://docs.docker.com/build/ci/github-actions/multi-platform/ | |
| docker-hub-build-and-publish: | |
| needs: [detect-code-related-file-changes] | |
| if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' | |
| timeout-minutes: 60 | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out the repo | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: 'true' | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v3 | |
| if: github.event_name == 'push' | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} | |
| - name: Get short commit SHA1 | |
| if: github.event_name == 'push' | |
| shell: bash | |
| run: | | |
| echo "short_hash=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" | |
| - name: Build and push | |
| if: github.event_name == 'push' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| push: true | |
| context: . | |
| platforms: linux/amd64,linux/arm64/v8 | |
| tags: sysprog21/rv32emu:latest, sysprog21/rv32emu:${{ env.short_hash }} |