Enable ThreadSanitizer across the entire multi-threaded JIT pipeline #3581
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@v4 | |
| - name: Test changed files | |
| id: changed-files | |
| uses: tj-actions/changed-files@v46 | |
| with: | |
| files: | | |
| .ci/** | |
| build/** | |
| mk/** | |
| src/** | |
| tests/** | |
| tools/** | |
| .clang-format | |
| Dockerfile | |
| Makefile | |
| - 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 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: 'true' | |
| - name: Cache LLVM 18 | |
| id: cache-llvm | |
| uses: actions/cache@v4 | |
| with: | |
| path: /usr/lib/llvm-18 | |
| key: ${{ runner.os }}-${{ runner.arch }}-llvm-18-v2 | |
| restore-keys: | | |
| ${{ runner.os }}-${{ runner.arch }}-llvm-18- | |
| - name: Cache RISC-V Toolchain | |
| id: cache-toolchain | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ github.workspace }}/toolchain | |
| key: ${{ runner.os }}-${{ runner.arch }}-riscv-toolchain-${{ hashFiles('.ci/riscv-toolchain-install.sh') }} | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update -q=2 | |
| sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full | |
| 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 18 | |
| 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 18 | |
| - name: Setup LLVM PATH | |
| run: echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH | |
| shell: bash | |
| - 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@v4 | |
| 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: | | |
| make artifact | |
| make ENABLE_SYSTEM=1 artifact | |
| make ENABLE_ARCH_TEST=1 artifact | |
| # 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 | |
| - name: default build using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make CC=emcc ENABLE_JIT=0 $PARALLEL | |
| - name: default build for system emulation using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make CC=emcc ENABLE_SYSTEM=1 ENABLE_JIT=0 $PARALLEL | |
| make distclean | |
| - name: Build with various optimization levels | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| for opt_level in -g -Og -O0 -O1 -O2 -O3 -Ofast; do | |
| echo "Building with OPT_LEVEL=$opt_level" | |
| if ! (make distclean && 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 | |
| for opt_level in -g -Og -O0 -O1 -O2 -O3 -Ofast; do | |
| echo "Building system emulation with OPT_LEVEL=$opt_level" | |
| if ! (make distclean && make OPT_LEVEL=$opt_level ENABLE_SYSTEM=1 $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 distclean | |
| make check $PARALLEL | |
| make tests $PARALLEL | |
| make misalign $PARALLEL | |
| make tool $PARALLEL | |
| - name: diverse configurations | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| for config in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \ | |
| ENABLE_SDL ENABLE_Zicsr ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs ENABLE_Zifencei; do | |
| echo "Testing with ${config}=0" | |
| if ! (make distclean && make ${config}=0 check $PARALLEL); then | |
| echo "ERROR: Test failed with ${config}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: misalignment test in block emulation | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make -C tests/system/alignment/ | |
| make distclean && make ENABLE_ELF_LOADER=1 ENABLE_EXT_C=0 ENABLE_SYSTEM=1 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 ENABLE_ELF_LOADER=1 ENABLE_SYSTEM=1 mmu-test $PARALLEL | |
| - name: gdbstub test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL | |
| - name: JIT test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Base JIT test | |
| make ENABLE_JIT=1 clean && make ENABLE_JIT=1 check $PARALLEL | |
| # JIT tests with disabled extensions | |
| for ext in ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C ENABLE_EXT_M \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \ | |
| ENABLE_Zicsr ENABLE_Zifencei \ | |
| ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do | |
| echo "JIT test with ${ext}=0" | |
| if ! (make ENABLE_JIT=1 clean && make ${ext}=0 ENABLE_JIT=1 check $PARALLEL); then | |
| echo "ERROR: JIT test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: undefined behavior test | |
| if: success() || failure() | |
| run: | | |
| make distclean && make ENABLE_UBSAN=1 check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_JIT=1 ENABLE_UBSAN=1 check $PARALLEL | |
| - name: ThreadSanitizer race detection test | |
| if: success() || failure() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -o pipefail | |
| # TSAN requires ASLR disabled to prevent allocations in shadow memory | |
| # Interpreter with FULL4G: Basic race detection across emulation core | |
| echo "=== TSAN Test 1/3: Interpreter + FULL4G ===" | |
| make distclean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 check $PARALLEL 2>&1 | tee tsan-interpreter.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-interpreter.log; then | |
| echo "ERROR: Data race detected in interpreter mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-interpreter.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in interpreter mode" | |
| # JIT tier-1: Race detection in template-based JIT compilation | |
| echo "=== TSAN Test 2/3: JIT Tier-1 ===" | |
| make ENABLE_JIT=1 clean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 check $PARALLEL 2>&1 | tee tsan-jit.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-jit.log; then | |
| echo "ERROR: Data race detected in JIT tier-1 mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-jit.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-1 mode" | |
| # JIT tier-2 (T2C): Race detection across LLVM compilation thread | |
| echo "=== TSAN Test 3/3: JIT Tier-2 (T2C) ===" | |
| make ENABLE_JIT=1 clean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 ENABLE_T2C=1 check $PARALLEL 2>&1 | tee tsan-t2c.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-t2c.log; then | |
| echo "ERROR: Data race detected in JIT tier-2 (T2C) mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-t2c.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-2 (T2C) mode" | |
| echo "=== All TSAN tests passed ===" | |
| - name: boot Linux kernel test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 $PARALLEL && make ENABLE_SYSTEM=1 artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make ENABLE_SYSTEM=1 clean | |
| - name: boot Linux kernel test (JIT) | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 $PARALLEL && make ENABLE_SYSTEM=1 artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 clean | |
| - name: Architecture test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| . .ci/common.sh | |
| export LATEST_RELEASE=$(download_with_headers "https://api.github.com/repos/sysprog21/rv32emu-prebuilt/releases" \ | |
| "Authorization: Bearer ${GH_TOKEN}" \ | |
| | grep '"tag_name"' \ | |
| | grep "sail" \ | |
| | head -n 1 \ | |
| | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') | |
| .ci/riscv-tests.sh | |
| 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 | |
| steps: | |
| - name: checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: 'true' | |
| - name: build artifact | |
| # The GitHub Action for non-x86 CPU | |
| uses: uraimo/run-on-arch-action@v3 | |
| with: | |
| arch: aarch64 | |
| distro: ubuntu24.04 | |
| githubToken: ${{ github.token }} | |
| # No 'sudo' is available | |
| install: | | |
| # Retry apt update with exponential backoff for mirror sync issues | |
| # dep11 = AppStream metadata (GUI app discovery, non-critical for CLI builds) | |
| # Critical files: Packages, Sources, Release, InRelease (binary/source indices) | |
| set -o pipefail | |
| APT_SUCCESS=0 | |
| for i in 1 2 3; do | |
| echo "=== apt update attempt $i/3 ===" | |
| if apt update -qq --allow-releaseinfo-change 2>&1 | tee /tmp/apt-update.log; then | |
| APT_EXIT=0 | |
| else | |
| APT_EXIT=$? | |
| fi | |
| # Check if log file was created | |
| if [ ! -f /tmp/apt-update.log ]; then | |
| echo "ERROR: apt update log file not created" | |
| if [ $i -lt 3 ]; then | |
| sleep $((i * 30)) | |
| continue | |
| else | |
| exit 1 | |
| fi | |
| fi | |
| # Check for critical package index failures (ignore dep11 metadata) | |
| # dep11 files like Components-arm64.yml.gz are non-critical (AppStream metadata) | |
| # Core package indices (Packages/Sources/Release/InRelease) MUST succeed | |
| if grep -q -E "Failed to fetch.*/(Packages|Sources|Release|InRelease)" /tmp/apt-update.log; then | |
| # Critical failure detected | |
| echo "ERROR: Critical package index files failed to download" | |
| grep -E "Failed to fetch.*/(Packages|Sources|Release|InRelease)" /tmp/apt-update.log | head -5 | |
| if [ $i -lt 3 ]; then | |
| delay=$((i * 30)) | |
| echo "Retrying in ${delay}s... (attempt $((i + 1))/3)" | |
| sleep $delay | |
| else | |
| echo "FATAL: Core package indices unavailable after 3 attempts" | |
| cat /tmp/apt-update.log | |
| exit 1 | |
| fi | |
| else | |
| # Success: core package indices available (dep11 failures OK) | |
| APT_SUCCESS=1 | |
| if [ $APT_EXIT -eq 0 ]; then | |
| echo "✓ apt update succeeded (all package lists available)" | |
| else | |
| echo "✓ apt update completed with warnings (exit=$APT_EXIT)" | |
| echo " Core package indices: AVAILABLE" | |
| if grep -q "dep11" /tmp/apt-update.log; then | |
| echo " dep11 metadata: INCOMPLETE (non-critical, GUI app metadata)" | |
| fi | |
| fi | |
| break | |
| fi | |
| done | |
| # Verify we succeeded in at least one attempt | |
| if [ $APT_SUCCESS -ne 1 ]; then | |
| echo "FATAL: apt update failed after all retry attempts" | |
| exit 1 | |
| fi | |
| # Install packages (exit 0 even if dep11 metadata is incomplete) | |
| echo "=== Installing build dependencies ===" | |
| apt install -yqq make git curl wget clang libsdl2-dev libsdl2-mixer-dev lsb-release software-properties-common gnupg bc 2>&1 | tee /tmp/apt-install.log || true | |
| # Verify critical packages were installed successfully | |
| echo "=== Verifying critical build tools ===" | |
| MISSING_PKGS="" | |
| for pkg in make git curl clang bc; do | |
| if ! command -v $pkg >/dev/null 2>&1; then | |
| MISSING_PKGS="$MISSING_PKGS $pkg" | |
| fi | |
| done | |
| if [ -n "$MISSING_PKGS" ]; then | |
| echo "ERROR: Critical packages failed to install:$MISSING_PKGS" | |
| echo "=== apt install log ===" | |
| cat /tmp/apt-install.log | |
| exit 1 | |
| fi | |
| echo "✓ All critical build tools installed successfully" | |
| # FIXME: gcc build fails on Aarch64/Linux hosts | |
| env: | | |
| CC: clang-18 | |
| # Append custom commands here | |
| run: | | |
| # Verify and install wget if needed (workaround for install step issues) | |
| if ! command -v wget > /dev/null 2>&1; then | |
| echo "wget not found, attempting to install..." | |
| apt update -qq --allow-releaseinfo-change 2>&1 | tee /tmp/apt-update-wget.log || true | |
| apt install -yqq wget 2>&1 | tee /tmp/wget-install.log || true | |
| if ! command -v wget > /dev/null 2>&1; then | |
| echo "ERROR: wget installation failed!" | |
| cat /tmp/wget-install.log | |
| exit 1 | |
| fi | |
| echo "wget installed successfully" | |
| fi | |
| git config --global --add safe.directory ${{ github.workspace }} | |
| git config --global --add safe.directory ${{ github.workspace }}/src/softfloat | |
| git config --global --add safe.directory ${{ github.workspace }}/src/mini-gdbstub | |
| wget -O /tmp/llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x /tmp/llvm.sh | |
| /tmp/llvm.sh 18 | |
| export PATH=/usr/lib/llvm-18/bin:$PATH | |
| PARALLEL=-j$(nproc) | |
| make artifact | |
| make $PARALLEL | |
| make check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_JIT=1 check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_EXT_A=0 ENABLE_JIT=1 check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_EXT_F=0 ENABLE_JIT=1 check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_EXT_C=0 ENABLE_JIT=1 check $PARALLEL | |
| # TSAN on ARM64: Fixed memory layout (0x150000000000 for main, 0x151000000000 for JIT) | |
| set -o pipefail | |
| echo "=== TSAN Test 1/3: Interpreter + FULL4G (ARM64) ===" | |
| make distclean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 check $PARALLEL 2>&1 | tee tsan-interpreter.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-interpreter.log; then | |
| echo "ERROR: Data race detected in interpreter mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-interpreter.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in interpreter mode" | |
| echo "=== TSAN Test 2/3: JIT Tier-1 (ARM64) ===" | |
| make ENABLE_JIT=1 clean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 check $PARALLEL 2>&1 | tee tsan-jit.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-jit.log; then | |
| echo "ERROR: Data race detected in JIT tier-1 mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-jit.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-1 mode" | |
| echo "=== TSAN Test 3/3: JIT Tier-2 (T2C) (ARM64) ===" | |
| make ENABLE_JIT=1 clean && setarch -R make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 ENABLE_T2C=1 check $PARALLEL 2>&1 | tee tsan-t2c.log | |
| if grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-t2c.log; then | |
| echo "ERROR: Data race detected in JIT tier-2 (T2C) mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-t2c.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-2 (T2C) mode" | |
| echo "=== All TSAN tests passed (ARM64) ===" | |
| 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 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: 'true' | |
| - name: Cache RISC-V Toolchain | |
| id: cache-toolchain | |
| uses: actions/cache@v4 | |
| 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@v4 | |
| with: | |
| path: | | |
| /opt/homebrew/Cellar/llvm@18 | |
| /opt/homebrew/Cellar/sdl2 | |
| /opt/homebrew/Cellar/dtc | |
| key: ${{ runner.os }}-${{ runner.arch }}-brew-${{ hashFiles('.github/workflows/main.yml') }} | |
| - name: Install dependencies | |
| run: | | |
| brew install make dtc expect sdl2 bc e2fsprogs p7zip llvm@18 dcfldd | |
| brew install sdl2_mixer || echo "Warning: sdl2_mixer installation failed, continuing without SDL_MIXER support" | |
| - 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@18)/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 }} | |
| - 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$(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: fetch artifact first to reduce HTTP requests | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| . .ci/common.sh | |
| LATEST_RELEASE=$(download_with_headers "https://api.github.com/repos/sysprog21/rv32emu-prebuilt/releases" \ | |
| "Authorization: Bearer ${GH_TOKEN}" \ | |
| | grep '"tag_name"' \ | |
| | grep "ELF" \ | |
| | head -n 1 \ | |
| | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') | |
| make LATEST_RELEASE=$LATEST_RELEASE artifact | |
| LATEST_RELEASE=$(download_with_headers "https://api.github.com/repos/sysprog21/rv32emu-prebuilt/releases" \ | |
| "Authorization: Bearer ${GH_TOKEN}" \ | |
| | grep '"tag_name"' \ | |
| | grep "Linux-Image" \ | |
| | head -n 1 \ | |
| | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') | |
| make LATEST_RELEASE=$LATEST_RELEASE ENABLE_SYSTEM=1 artifact | |
| LATEST_RELEASE=$(download_with_headers "https://api.github.com/repos/sysprog21/rv32emu-prebuilt/releases" \ | |
| "Authorization: Bearer ${GH_TOKEN}" \ | |
| | grep '"tag_name"' \ | |
| | grep "sail" \ | |
| | head -n 1 \ | |
| | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') | |
| make LATEST_RELEASE=$LATEST_RELEASE ENABLE_ARCH_TEST=1 artifact | |
| # 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 | |
| - name: default build using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make CC=emcc ENABLE_JIT=0 $PARALLEL | |
| - name: default build for system emulation using emcc | |
| if: success() | |
| run: | | |
| make distclean | |
| make CC=emcc ENABLE_SYSTEM=1 ENABLE_JIT=0 $PARALLEL | |
| make distclean | |
| - name: check + tests | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean | |
| make check $PARALLEL | |
| make tests $PARALLEL | |
| make misalign $PARALLEL | |
| make tool $PARALLEL | |
| - name: diverse configurations | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| for config in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \ | |
| ENABLE_SDL ENABLE_Zicsr ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs ENABLE_Zifencei; do | |
| echo "Testing with ${config}=0" | |
| if ! (make distclean && make ${config}=0 check $PARALLEL); then | |
| echo "ERROR: Test failed with ${config}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: gdbstub test, need RV32 toolchain | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL | |
| - name: JIT test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -euo pipefail | |
| # Base JIT test | |
| make ENABLE_JIT=1 clean && make ENABLE_JIT=1 check $PARALLEL | |
| # JIT tests with disabled extensions | |
| for ext in ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C ENABLE_EXT_M \ | |
| ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \ | |
| ENABLE_Zicsr ENABLE_Zifencei \ | |
| ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do | |
| echo "JIT test with ${ext}=0" | |
| if ! (make ENABLE_JIT=1 clean && make ${ext}=0 ENABLE_JIT=1 check $PARALLEL); then | |
| echo "ERROR: JIT test failed with ${ext}=0" | |
| exit 1 | |
| fi | |
| done | |
| - name: JIT debug test | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| # Run JIT tests with debug mode to catch register allocation and cache coherency issues | |
| make distclean && make ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL | |
| make distclean && make ENABLE_EXT_C=0 ENABLE_JIT=1 ENABLE_JIT_DEBUG=1 check $PARALLEL | |
| if: ${{ always() }} | |
| - 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 ENABLE_UBSAN=1 check $PARALLEL | |
| make ENABLE_JIT=1 clean && make ENABLE_JIT=1 ENABLE_UBSAN=1 check $PARALLEL | |
| - name: ThreadSanitizer race detection test | |
| if: (success() || failure()) && steps.install_cc.outputs.cc == 'clang' # Only clang supports TSAN on macOS | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| set -o pipefail | |
| # macOS TSAN: Fixed memory at 0x150000000000 (main) and 0x151000000000 (JIT) | |
| # Note: ASLR disabled via mmap(MAP_FIXED), but SIP may restrict full ASLR control on GitHub runners | |
| # Test 1: Interpreter + FULL4G | |
| echo "=== TSAN Test 1/3: Interpreter + FULL4G (macOS ARM64) ===" | |
| make distclean && make ENABLE_TSAN=1 ENABLE_FULL4G=1 check $PARALLEL 2>&1 | tee tsan-interpreter.log || { | |
| # Check if failure is due to MAP_FIXED restriction vs actual race | |
| if grep -q "MAP_FAILED\|unexpected memory mapping\|FATAL: ThreadSanitizer" tsan-interpreter.log; then | |
| echo "⚠️ TSAN memory allocation failed (SIP/ASLR restriction) - test skipped" | |
| else | |
| echo "ERROR: Test execution failed" | |
| cat tsan-interpreter.log | |
| exit 1 | |
| fi | |
| } | |
| if [ -f tsan-interpreter.log ] && grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-interpreter.log; then | |
| echo "ERROR: Data race detected in interpreter mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-interpreter.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in interpreter mode" | |
| # Test 2: JIT tier-1 | |
| echo "=== TSAN Test 2/3: JIT Tier-1 (macOS ARM64) ===" | |
| make ENABLE_JIT=1 clean && make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 check $PARALLEL 2>&1 | tee tsan-jit.log || { | |
| if grep -q "MAP_FAILED\|unexpected memory mapping\|FATAL: ThreadSanitizer" tsan-jit.log; then | |
| echo "⚠️ TSAN memory allocation failed (SIP/ASLR restriction) - test skipped" | |
| else | |
| echo "ERROR: Test execution failed" | |
| cat tsan-jit.log | |
| exit 1 | |
| fi | |
| } | |
| if [ -f tsan-jit.log ] && grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-jit.log; then | |
| echo "ERROR: Data race detected in JIT tier-1 mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-jit.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-1 mode" | |
| # Test 3: JIT tier-2 (T2C) | |
| echo "=== TSAN Test 3/3: JIT Tier-2 (T2C) (macOS ARM64) ===" | |
| make ENABLE_JIT=1 clean && make ENABLE_TSAN=1 ENABLE_FULL4G=1 ENABLE_JIT=1 ENABLE_T2C=1 check $PARALLEL 2>&1 | tee tsan-t2c.log || { | |
| if grep -q "MAP_FAILED\|unexpected memory mapping\|FATAL: ThreadSanitizer" tsan-t2c.log; then | |
| echo "⚠️ TSAN memory allocation failed (SIP/ASLR restriction) - test skipped" | |
| else | |
| echo "ERROR: Test execution failed" | |
| cat tsan-t2c.log | |
| exit 1 | |
| fi | |
| } | |
| if [ -f tsan-t2c.log ] && grep -q "ThreadSanitizer: data race\|ThreadSanitizer: race on\|WARNING: ThreadSanitizer:" tsan-t2c.log; then | |
| echo "ERROR: Data race detected in JIT tier-2 (T2C) mode!" | |
| grep -A 10 "ThreadSanitizer:" tsan-t2c.log | |
| exit 1 | |
| fi | |
| echo "✓ No races detected in JIT tier-2 (T2C) mode" | |
| echo "=== All TSAN tests completed (macOS ARM64) ===" | |
| - name: boot Linux kernel test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 $PARALLEL && \ | |
| make ENABLE_SYSTEM=1 artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make ENABLE_SYSTEM=1 clean | |
| - name: boot Linux kernel test (JIT) | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| run: | | |
| make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 $PARALLEL && make ENABLE_SYSTEM=1 artifact $PARALLEL | |
| bash -c "${BOOT_LINUX_TEST}" | |
| make ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 clean | |
| - name: Architecture test | |
| if: success() | |
| env: | |
| CC: ${{ steps.install_cc.outputs.cc }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| . .ci/common.sh | |
| export LATEST_RELEASE=$(download_with_headers "https://api.github.com/repos/sysprog21/rv32emu-prebuilt/releases" \ | |
| "Authorization: Bearer ${GH_TOKEN}" \ | |
| | grep '"tag_name"' \ | |
| | grep "sail" \ | |
| | head -n 1 \ | |
| | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') | |
| 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@v4 | |
| - name: coding convention | |
| run: | | |
| sudo apt-get install -q=2 clang-format-18 shfmt python3-pip | |
| pip3 install black==25.1.0 | |
| .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@v4 | |
| with: | |
| submodules: 'true' | |
| # LLVM static analysis | |
| - name: set up scan-build | |
| run: | | |
| sudo apt-get update -q=2 | |
| sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev | |
| .ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x ./llvm.sh | |
| sudo ./llvm.sh 18 | |
| sudo apt-get install -q=2 clang-18 clang-tools-18 | |
| shell: bash | |
| - name: run scan-build without JIT | |
| env: | |
| LATEST_RELEASE: dummy | |
| run: make distclean && scan-build-18 -v -o ~/scan-build --status-bugs --use-cc=clang-18 --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: | | |
| make ENABLE_JIT=1 distclean && scan-build-18 -v -o ~/scan-build --status-bugs --use-cc=clang-18 --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@v4 | |
| 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 }} |