Skip to content

Enable ThreadSanitizer across the entire multi-threaded JIT pipeline #3581

Enable ThreadSanitizer across the entire multi-threaded JIT pipeline

Enable ThreadSanitizer across the entire multi-threaded JIT pipeline #3581

Workflow file for this run

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 }}