Skip to content

fix: free table68k + branch_condition_table on shutdown (closes #125) #280

fix: free table68k + branch_condition_table on shutdown (closes #125)

fix: free table68k + branch_condition_table on shutdown (closes #125) #280

Workflow file for this run

name: C/C++ CI
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ develop, master ]
workflow_dispatch:
# Cancel in-progress runs when a new commit lands on the same PR.
# Pushes to master/develop run to completion (no cancellation) so we
# always have a green-or-red record on the integration branches.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
strategy:
fail-fast: false
matrix:
config:
# Linux
- displayTargetName: 'Linux x86_64 (GCC)'
artifact: 'virtualjaguar_libretro.so'
os: ubuntu-latest
cc: 'gcc'
cxx: 'g++'
- displayTargetName: 'Linux x86_64 (Clang)'
artifact: 'virtualjaguar_libretro.so'
os: ubuntu-latest
cc: 'clang'
cxx: 'clang++'
- displayTargetName: 'Linux aarch64'
artifact: 'virtualjaguar_libretro.so'
os: ubuntu-24.04-arm
cc: 'gcc'
cxx: 'g++'
- displayTargetName: 'Linux i686'
artifact: 'virtualjaguar_libretro.so'
os: ubuntu-latest
cc: 'gcc -m32'
cxx: 'g++ -m32'
multilib: true
# macOS
- displayTargetName: 'macOS arm64 (Clang)'
artifact: 'virtualjaguar_libretro.dylib'
os: macos-latest
cc: 'clang'
cxx: 'clang++'
# Cross-compiled from macos-latest (arm64) -- avoids the
# scarce macos-13 Intel runner pool. See release.yml for
# rationale.
- displayTargetName: 'macOS x86_64 (Clang, cross)'
artifact: 'virtualjaguar_libretro.dylib'
os: macos-latest
cc: 'clang -arch x86_64'
cxx: 'clang++ -arch x86_64'
make_extra: 'BLITTER_SIMD=sse2'
cross: true
# Windows (MinGW)
- displayTargetName: 'Windows x86_64 (MSYS2)'
artifact: 'virtualjaguar_libretro.dll'
os: windows-latest
cc: 'gcc'
cxx: 'g++'
shell: 'msys2 {0}'
msystem: 'MINGW64'
msys2_packages: 'mingw-w64-x86_64-gcc make'
- displayTargetName: 'Windows i686 (MSYS2)'
artifact: 'virtualjaguar_libretro.dll'
os: windows-latest
cc: 'gcc'
cxx: 'g++'
shell: 'msys2 {0}'
msystem: 'MINGW32'
msys2_packages: 'mingw-w64-i686-gcc make'
# Emscripten (WebAssembly)
- displayTargetName: 'Emscripten (WASM)'
artifact: 'virtualjaguar_libretro_emscripten.bc'
os: ubuntu-latest
emscripten: true
# Android NDK
- displayTargetName: 'Android arm64-v8a'
artifact: 'libs/arm64-v8a/libretro.so'
os: ubuntu-latest
android: true
android_abi: 'arm64-v8a'
- displayTargetName: 'Android armeabi-v7a'
artifact: 'libs/armeabi-v7a/libretro.so'
os: ubuntu-latest
android: true
android_abi: 'armeabi-v7a'
- displayTargetName: 'Android x86_64'
artifact: 'libs/x86_64/libretro.so'
os: ubuntu-latest
android: true
android_abi: 'x86_64'
- displayTargetName: 'Android x86'
artifact: 'libs/x86/libretro.so'
os: ubuntu-latest
android: true
android_abi: 'x86'
# iOS / tvOS
- displayTargetName: 'iOS arm64'
artifact: 'virtualjaguar_libretro_ios.dylib'
os: macos-latest
make_platform: 'ios-arm64'
cross: true
- displayTargetName: 'tvOS arm64'
artifact: 'virtualjaguar_libretro_tvos.dylib'
os: macos-latest
make_platform: 'tvos-arm64'
cross: true
name: build-${{ matrix.config.displayTargetName }}
runs-on: ${{ matrix.config.os }}
defaults:
run:
shell: ${{ matrix.config.shell || 'bash' }}
steps:
- uses: actions/checkout@v5
- name: Install multilib
if: matrix.config.multilib
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y gcc-multilib g++-multilib
- name: Set up MSYS2
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.config.msystem || 'MINGW64' }}
update: false
install: ${{ matrix.config.msys2_packages || 'mingw-w64-x86_64-gcc make' }}
- name: Set up Emscripten
if: matrix.config.emscripten
uses: mymindstorm/setup-emsdk@v16
- name: Set up Android NDK
if: matrix.config.android
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26d
- name: Build
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.make_platform }}
run: make -j4 CC="${{ matrix.config.cc }}" CXX="${{ matrix.config.cxx }}" ${{ matrix.config.make_extra || '' }}
- name: Build (platform)
if: matrix.config.make_platform
run: make -j4 platform=${{ matrix.config.make_platform }}
- name: Build (Emscripten)
if: matrix.config.emscripten
run: emmake make -j4 platform=emscripten
- name: Build (Android NDK)
if: matrix.config.android
run: |
# ndk-build uses jni/Android.mk (not the project Makefile),
# so the parse-time version.h regen in Makefile doesn't run.
bash scripts/gen-version-h.sh
${{ steps.setup-ndk.outputs.ndk-path }}/ndk-build \
APP_ABI=${{ matrix.config.android_abi }} -j4
# Mach-O symbol gating: production builds must export retro_* only.
# Runs before the test step (which would relink with TEST_EXPORTS=1
# and widen the symbol set).
- name: Verify Mach-O symbol gating (production)
if: ${{ runner.os == 'macOS' && !matrix.config.emscripten && !matrix.config.android }}
run: |
LEAKS=$(nm -gU "${{ matrix.config.artifact }}" | grep -v ' _retro_' || true)
if [ -n "$LEAKS" ]; then
echo "::error::Production dylib leaked non-retro_* symbols:"
echo "$LEAKS"
exit 1
fi
COUNT=$(nm -gU "${{ matrix.config.artifact }}" | wc -l | tr -d ' ')
echo "==> Production exports: ${COUNT} (all retro_*)"
# Host/native toolchains only — skips cross-compile rows (e.g. aarch64 on x86 runner).
- name: Run cheat engine unit tests
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
run: make test CC="${{ matrix.config.cc }}"
- name: Run SIMD blitter tests
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross }}
run: |
ARCH=$(uname -m)
CC="${{ matrix.config.cc }}"
case "$ARCH" in
x86_64|i686|i386) SIMD_SRC=src/tom/blitter_simd_sse2.c; EXTRA="-msse2" ;;
aarch64|arm64) SIMD_SRC=src/tom/blitter_simd_neon.c; EXTRA="" ;;
*) SIMD_SRC=src/tom/blitter_simd_scalar.c; EXTRA="" ;;
esac
echo "==> Testing ${SIMD_SRC}..."
$CC -O2 -Wall ${EXTRA} -I src -I src/core -I src/tom \
-o test_blitter_simd test/test_blitter_simd.c ${SIMD_SRC}
./test_blitter_simd
echo "==> Cross-checking against scalar..."
$CC -O2 -Wall -I src -I src/core -I src/tom \
-o test_blitter_scalar test/test_blitter_simd.c src/tom/blitter_simd_scalar.c
./test_blitter_scalar
echo "==> DSP 40-bit MAC accumulator regression (dsp_acc40.h)..."
$CC -O2 -Wall -I src -I src/jerry -o test_dsp_mac40 test/test_dsp_mac40.c
./test_dsp_mac40
- name: Run memory map test
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
run: |
CC="${{ matrix.config.cc }}"
if [ "$(uname)" = "Linux" ]; then
LDFLAGS="-ldl"
else
LDFLAGS=""
fi
$CC -O2 -Wall -o test/tools/test_memory_map test/tools/test_memory_map.c $LDFLAGS
./test/tools/test_memory_map ./${{ matrix.config.artifact }}
- name: Run DSP instruction set tests
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
run: |
CC="${{ matrix.config.cc }}"
if [ "$(uname)" = "Linux" ]; then LDFLAGS="-ldl"; else LDFLAGS=""; fi
$CC -O2 -Wall -o test/test_dsp_ops test/test_dsp_ops.c $LDFLAGS
$CC -O2 -Wall -o test/test_dsp_unit test/test_dsp_unit.c $LDFLAGS
./test/test_dsp_ops
./test/test_dsp_unit
- name: Run GPU instruction set tests
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
run: |
CC="${{ matrix.config.cc }}"
if [ "$(uname)" = "Linux" ]; then LDFLAGS="-ldl"; else LDFLAGS=""; fi
$CC -O2 -Wall -o test/test_gpu_ops test/test_gpu_ops.c $LDFLAGS
./test/test_gpu_ops
$CC -O2 -Wall -o test/test_op_gpu_object test/test_op_gpu_object.c $LDFLAGS
./test/test_op_gpu_object ./${{ matrix.config.artifact }}
- name: Run 68K instruction set tests
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
run: |
CC="${{ matrix.config.cc }}"
if [ "$(uname)" = "Linux" ]; then LDFLAGS="-ldl"; else LDFLAGS=""; fi
$CC -O2 -Wall -o test/test_m68k_ops test/test_m68k_ops.c $LDFLAGS
./test/test_m68k_ops
- name: Cache pinned rcheevos E2E build
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
uses: actions/cache@v5
with:
path: build/rcheevos-static
key: rcheevos-e2e-fd57e900-${{ runner.os }}-${{ matrix.config.cc }}-${{ matrix.config.displayTargetName }}
- name: RetroAchievements rc_libretro E2E test
if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.cross && runner.os != 'Windows' }}
env:
# Immutable pin (rcheevos v12.3.0); tarball via .../archive/${SHA}.tar.gz
RCHEEVOS_REF: fd57e900758a9e3de343e7b0316b8a6059dce228
CC: ${{ matrix.config.cc }}
run: bash test/tools/test_rcheevos_e2e.sh ./${{ matrix.config.artifact }}
- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: ${{ matrix.config.displayTargetName }}
path: ${{ matrix.config.artifact }}
if-no-files-found: error
msvc-check:
name: MSVC ${{ matrix.arch }} compilation check
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
arch: [x64, x86]
steps:
- uses: actions/checkout@v5
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.arch }}
- name: Generate src/core/version.h
shell: bash
run: bash scripts/gen-version-h.sh
- name: Compile all sources with cl.exe
shell: cmd
run: |
cl.exe /c /W3 /O2 /DNDEBUG /D_CRT_SECURE_NO_DEPRECATE ^
/I. /Isrc /Isrc\core /Isrc\tom /Isrc\jerry /Isrc\cd /Isrc\bios /Isrc\m68000 /Ilibretro-common\include ^
/D__LIBRETRO__ /DINLINE="_inline" ^
libretro.c ^
src\tom\blitter.c src\tom\blitter_compare.c src\tom\blitter_mmio.c src\jerry\dac.c src\jerry\dsp.c src\core\file.c ^
src\tom\gpu.c src\core\jaguar.c src\jerry\jerry.c src\tom\tom.c src\tom\op.c ^
src\cd\cdintf.c src\cd\cdrom.c src\core\crc32.c src\core\event.c ^
src\jerry\eeprom.c src\core\filedb.c src\jerry\joystick.c src\core\settings.c ^
src\core\memtrack.c src\core\vjag_memory.c src\core\cheat.c ^
src\core\universalhdr.c src\jerry\wavetable.c ^
src\bios\jagbios.c ^
src\bios\jagcdbios.c src\bios\jagdevcdbios.c ^
src\bios\jagstub1bios.c src\bios\jagstub2bios.c ^
src\m68000\m68kinterface.c ^
src\tom\blitter_simd_scalar.c ^
src\tom\blitter_simd_sse2.c
echo MSVC compilation check passed
vita-build:
name: build-PS Vita
runs-on: ubuntu-latest
container:
image: vitasdk/vitasdk:latest
steps:
- uses: actions/checkout@v5
- name: Build
run: make -j4 platform=vita
- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: PS Vita
path: virtualjaguar_libretro_vita.a
if-no-files-found: error
switch-build:
name: build-Nintendo Switch
runs-on: ubuntu-latest
container:
image: devkitpro/devkita64:latest
steps:
- uses: actions/checkout@v5
- name: Build
run: make -j4 platform=libnx
env:
DEVKITPRO: /opt/devkitpro
- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: Nintendo Switch
path: virtualjaguar_libretro_libnx.a
if-no-files-found: error
editorconfig-lint:
name: EditorConfig compliance check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install editorconfig-checker
run: |
VERSION=v3.0.3
curl -fsSL "https://github.com/editorconfig-checker/editorconfig-checker/releases/download/${VERSION}/ec-linux-amd64.tar.gz" \
| tar -xz -C /tmp
sudo install -m 0755 /tmp/bin/ec-linux-amd64 /usr/local/bin/ec
- name: Run editorconfig-checker
run: ec -verbose
coverage:
name: Code coverage (gcov + codecov)
runs-on: ubuntu-latest
# Coverage runs are advisory -- they don't gate merges. The PR
# delta is what reviewers care about; codecov posts that as a
# comment automatically.
continue-on-error: true
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install gcov + gcovr
run: |
sudo apt-get update
sudo apt-get install -y gcc gcovr
- name: make coverage
run: |
bash scripts/gen-version-h.sh
make coverage CC="gcc" CXX="g++"
- name: Upload to Codecov
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
fail_ci_if_error: false
# Token optional for public repos; if set in repo secrets it
# avoids the anonymous-upload rate limit.
token: ${{ secrets.CODECOV_TOKEN }}
sanitizers:
name: AddressSanitizer + UndefinedBehaviorSanitizer
runs-on: ubuntu-latest
# Initially advisory: lets the job report findings without blocking
# PRs while we triage the existing UB the cppcheck job already
# flagged (signed shifts in src/tom/{gpu,op}.c, etc.). Flip to
# `false` to make sanitizer findings gate merges.
continue-on-error: true
steps:
- uses: actions/checkout@v5
- name: Install clang
run: sudo apt-get update && sudo apt-get install -y clang
- name: Build with ASAN + UBSAN
run: |
bash scripts/gen-version-h.sh
# -shared-libasan: when building a shared library with
# -fsanitize, clang doesn't link the static asan/ubsan
# runtime into the .so (it's expected to be loaded by the
# host executable). --no-undefined in the SHARED rule then
# rejects the runtime symbols. -shared-libasan switches to
# the dynamic libclang_rt.asan-x86_64.so which the host
# process picks up at dlopen time.
SAN_FLAGS="-fsanitize=address,undefined -shared-libasan -fno-omit-frame-pointer -O1 -g -fsanitize-ignorelist=$PWD/.ubsan-ignorelist"
make -j4 TEST_EXPORTS=1 \
CC="clang $SAN_FLAGS" \
CXX="clang++ $SAN_FLAGS" \
LD="clang $SAN_FLAGS"
- name: Run test suite under sanitizers
env:
ASAN_OPTIONS: 'detect_leaks=1:abort_on_error=1:print_stacktrace=1'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:report_error_type=1'
run: |
SAN_FLAGS="-fsanitize=address,undefined -shared-libasan -fno-omit-frame-pointer -O1 -g -fsanitize-ignorelist=$PWD/.ubsan-ignorelist"
# The shared-libasan path means the test binaries need to find
# libclang_rt.asan at runtime; expose its directory.
ASAN_LIB_DIR=$(clang -print-resource-dir)/lib/linux
export LD_LIBRARY_PATH="$ASAN_LIB_DIR:${LD_LIBRARY_PATH:-}"
make test TEST_EXPORTS=1 \
CC="clang $SAN_FLAGS" \
CXX="clang++ $SAN_FLAGS" \
LD="clang $SAN_FLAGS"
clang-tidy:
name: clang-tidy on changed files
runs-on: ubuntu-latest
# Advisory at first. The .clang-tidy check list is curated, but
# the first pass over emulator C will surface noisy true positives
# that need triage. Flip continue-on-error to false once the
# baseline is calm.
continue-on-error: true
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install clang-tidy + bear
run: |
sudo apt-get update
sudo apt-get install -y clang-tidy bear
- name: Generate compile_commands.json
run: |
bash scripts/gen-version-h.sh
# `bear -- make` records every compile invocation into
# compile_commands.json without affecting the build.
bear -- make -j4
- name: Run clang-tidy on PR-changed files
run: |
BASE="${{ github.event.pull_request.base.sha }}"
FILES=$(git diff --name-only "$BASE"...HEAD -- '*.c' '*.h' \
| grep -Ev '^(src/m68000/|src/bios/jag.*\.c$|libretro-common/|src/core/version\.h$)' \
| grep -E '^(src/|libretro\.c$)' || true)
if [ -z "$FILES" ]; then
echo "No relevant C files changed; skipping clang-tidy."
exit 0
fi
echo "==> Running clang-tidy on:"
echo "$FILES"
echo "$FILES" | xargs clang-tidy -p . --quiet --warnings-as-errors='*'
cppcheck:
name: cppcheck static analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install cppcheck
run: sudo apt-get update && sudo apt-get install -y cppcheck
- name: Run cppcheck
run: |
# Scan in-tree sources only. src/m68000 is machine-generated
# UAE 68K, src/bios/jag*.c are bin2c hex tables, and
# libretro-common is a vendored subtree -- all excluded.
#
# Suppressions live in cppcheck-suppressions.txt with rationale.
# cppcheck 2.13 (Ubuntu 24.04) doesn't accept comment lines in
# the suppressions file; strip them before passing.
bash scripts/gen-version-h.sh
# POSIX character class -- not GNU grep's \s extension.
grep -vE '^[[:space:]]*(#|$)' cppcheck-suppressions.txt > /tmp/cppcheck-suppressions.txt
cppcheck \
--enable=warning,performance,portability \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--suppressions-list=/tmp/cppcheck-suppressions.txt \
--error-exitcode=1 \
-i src/m68000 -i src/bios -i libretro-common \
-I src -I src/core -I src/tom -I src/jerry -I src/cd \
-I src/bios -I src/m68000 -I libretro-common/include \
-DINLINE=inline -D__LIBRETRO__ \
--quiet \
src/ libretro.c
c89-lint:
name: C89 compliance check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Check for declaration-after-statement
run: |
echo "==> Checking C89 compliance (catches MSVC C89 errors)..."
scripts/c89-lint.sh
- name: Verify .info display_version matches Makefile
run: bash scripts/check-info-version.sh
- name: Check for stdbool.h usage (use boolean.h instead)
run: |
echo "==> Checking for direct stdbool.h includes..."
FAILED=0
for f in libretro.c $(git ls-files 'src/**/*.c' 'src/**/*.h'); do
case "$f" in
src/core/boolean.h) continue ;;
esac
if grep -n '#include.*<stdbool\.h>' "$f" 2>/dev/null; then
echo "::error file=$f::Use <boolean.h> instead of <stdbool.h> (MSVC 2005/2010 compat)"
FAILED=1
fi
done
if [ "$FAILED" = "1" ]; then
exit 1
fi
echo "==> No direct stdbool.h includes found"