From ce3187bee1dfe7d0adcb9dbdc3ad0af4cdde82ea Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 22 Apr 2026 21:36:54 -0400 Subject: [PATCH 1/3] Fix CI buildbot failures, expand CI coverage, add release automation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code fixes: - libretro.c: Move extern declarations to top of function blocks for MSVC 2005/2010 C89 compliance (error C2143 mid-block declarations) - blitter_simd_sse2.c: Replace _mm_cvtsi128_si64 with memcpy-based helper — the former only exists on x86_64, breaking 32-bit x86 builds (Linux i686, Windows i686 MinGW). Also fix C89 mid-block declarations. - Makefile.common: Detect cross-compiler prefixes (arm-, aarch64-, mips, powerpc) in CC to skip host uname -m SIMD fallback. Fixes webOS ARM build getting SSE2 when built on an x86_64 host. CI coverage (c-cpp.yml — 13 build targets + C89 lint): - Add Windows i686 MinGW (MSYS2 MINGW32) - Add Linux i686 (gcc -m32 multilib) - Add Android NDK arm64-v8a + armeabi-v7a - Add iOS arm64 + tvOS arm64 (Xcode cross-compile) - Add C89 compliance lint job - Add workflow_dispatch for manual trigger via GitHub UI - Parameterize MSYS2 setup (msystem, packages) per matrix entry Release automation: - release.yml: Sync build matrix (12 platform artifacts including iOS/tvOS) - version-bump.yml: New workflow_dispatch to bump version in libretro.c, commit, tag, and push — triggering release.yml automatically .gitignore: Comprehensive ignore rules for build artifacts, test binaries, logs, ROMs, IDE files, Python venvs, and reference docs. Made-with: Cursor --- .github/workflows/c-cpp.yml | 117 ++++++++++++++++++++++++++--- .github/workflows/release.yml | 99 +++++++++++++++++++++--- .github/workflows/version-bump.yml | 77 +++++++++++++++++++ .gitignore | 37 +++++++++ Makefile.common | 25 +++--- jni/Android.mk | 4 +- libretro.c | 8 +- src/blitter_simd_sse2.c | 27 +++++-- src/dsp.c | 5 +- 9 files changed, 353 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/version-bump.yml diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index c80e3844..d021f947 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: build: @@ -31,6 +32,13 @@ jobs: 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' @@ -45,6 +53,17 @@ jobs: 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)' @@ -52,6 +71,34 @@ jobs: os: ubuntu-latest emscripten: true + # Android NDK (arm64-v8a) + - displayTargetName: 'Android arm64-v8a' + artifact: 'libs/arm64-v8a/libretro.so' + os: ubuntu-latest + android: true + android_abi: 'arm64-v8a' + + # Android NDK (armeabi-v7a) + - displayTargetName: 'Android armeabi-v7a' + artifact: 'libs/armeabi-v7a/libretro.so' + os: ubuntu-latest + android: true + android_abi: 'armeabi-v7a' + + # iOS + - displayTargetName: 'iOS arm64' + artifact: 'virtualjaguar_libretro_ios.dylib' + os: macos-latest + make_platform: 'ios-arm64' + cross: true + + # tvOS + - 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 }} @@ -62,32 +109,55 @@ jobs: steps: - uses: actions/checkout@v4 + - 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: MINGW64 + msystem: ${{ matrix.config.msystem || 'MINGW64' }} update: false - install: >- - mingw-w64-x86_64-gcc - make + install: ${{ matrix.config.msys2_packages || 'mingw-w64-x86_64-gcc make' }} - name: Set up Emscripten if: matrix.config.emscripten uses: mymindstorm/setup-emsdk@v14 + - 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 }} - run: make -j4 CC=${{ matrix.config.cc }} CXX=${{ matrix.config.cxx }} + if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.make_platform }} + run: make -j4 CC="${{ matrix.config.cc }}" CXX="${{ matrix.config.cxx }}" + + - 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: | + ${{ steps.setup-ndk.outputs.ndk-path }}/ndk-build \ + APP_ABI=${{ matrix.config.android_abi }} -j4 + - name: Run SIMD blitter tests - if: ${{ !matrix.config.emscripten }} + 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/blitter_simd_sse2.c; EXTRA="-msse2" ;; aarch64|arm64) SIMD_SRC=src/blitter_simd_neon.c; EXTRA="" ;; @@ -95,12 +165,12 @@ jobs: esac echo "==> Testing ${SIMD_SRC}..." - ${{ matrix.config.cc }} -O2 -Wall ${EXTRA} -I src \ + $CC -O2 -Wall ${EXTRA} -I src \ -o test_blitter_simd test/test_blitter_simd.c ${SIMD_SRC} ./test_blitter_simd echo "==> Cross-checking against scalar..." - ${{ matrix.config.cc }} -O2 -Wall -I src \ + $CC -O2 -Wall -I src \ -o test_blitter_scalar test/test_blitter_simd.c src/blitter_simd_scalar.c ./test_blitter_scalar @@ -110,3 +180,32 @@ jobs: name: ${{ matrix.config.displayTargetName }} path: ${{ matrix.config.artifact }} if-no-files-found: error + + c89-lint: + name: C89 compliance check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check for declaration-after-statement + run: | + echo "==> Checking C89 compliance (catches MSVC C89 errors)..." + FAILED=0 + for f in libretro.c src/*.c; do + # Skip machine-generated files + case "$f" in + src/m68000/*|src/jag*bios*.c|src/jagstub*bios.c|src/blitter_simd_neon.c|src/blitter_simd_sse2.c) continue ;; + esac + if ! gcc -fsyntax-only -std=gnu89 \ + -Werror=declaration-after-statement \ + -I. -Isrc -Isrc/m68000 -Ilibretro-common/include \ + -D__LIBRETRO__ -DINLINE="inline" \ + "$f" 2>&1; then + FAILED=1 + fi + done + if [ "$FAILED" = "1" ]; then + echo "::error::C89 compliance check failed — mid-block declarations found" + exit 1 + fi + echo "==> All files pass C89 declaration check" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10463920..8ffa501c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,7 @@ jobs: fail-fast: false matrix: config: + # Linux - platform: linux-x86_64 artifact: virtualjaguar_libretro.so os: ubuntu-latest @@ -25,18 +26,69 @@ jobs: cc: gcc cxx: g++ + - platform: linux-i686 + artifact: virtualjaguar_libretro.so + os: ubuntu-latest + cc: 'gcc -m32' + cxx: 'g++ -m32' + multilib: true + + # macOS - platform: macos-arm64 artifact: virtualjaguar_libretro.dylib os: macos-latest cc: clang cxx: clang++ + # Windows - platform: windows-x86_64 artifact: virtualjaguar_libretro.dll os: windows-latest cc: gcc cxx: g++ shell: 'msys2 {0}' + msystem: 'MINGW64' + msys2_packages: 'mingw-w64-x86_64-gcc make' + + - platform: windows-i686 + 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) + - platform: emscripten-wasm + artifact: virtualjaguar_libretro_emscripten.bc + os: ubuntu-latest + emscripten: true + + # Android NDK + - platform: android-arm64-v8a + artifact: libs/arm64-v8a/libretro.so + os: ubuntu-latest + android: true + android_abi: 'arm64-v8a' + + - platform: android-armeabi-v7a + artifact: libs/armeabi-v7a/libretro.so + os: ubuntu-latest + android: true + android_abi: 'armeabi-v7a' + + # iOS + - platform: ios-arm64 + artifact: virtualjaguar_libretro_ios.dylib + os: macos-latest + make_platform: 'ios-arm64' + + # tvOS + - platform: tvos-arm64 + artifact: virtualjaguar_libretro_tvos.dylib + os: macos-latest + make_platform: 'tvos-arm64' name: build-${{ matrix.config.platform }} runs-on: ${{ matrix.config.os }} @@ -48,25 +100,54 @@ jobs: steps: - uses: actions/checkout@v4 + - 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: MINGW64 + msystem: ${{ matrix.config.msystem || 'MINGW64' }} update: false - install: >- - mingw-w64-x86_64-gcc - make + install: ${{ matrix.config.msys2_packages || 'mingw-w64-x86_64-gcc make' }} + + - name: Set up Emscripten + if: matrix.config.emscripten + uses: mymindstorm/setup-emsdk@v14 + + - name: Set up Android NDK + if: matrix.config.android + uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r26d - name: Build - run: make -j4 CC=${{ matrix.config.cc }} CXX=${{ matrix.config.cxx }} + if: ${{ !matrix.config.emscripten && !matrix.config.android && !matrix.config.make_platform }} + run: make -j4 CC="${{ matrix.config.cc }}" CXX="${{ matrix.config.cxx }}" + + - 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: | + ${{ steps.setup-ndk.outputs.ndk-path }}/ndk-build \ + APP_ABI=${{ matrix.config.android_abi }} -j4 - name: Package run: | mkdir -p dist - cp ${{ matrix.config.artifact }} dist/virtualjaguar_libretro-${{ matrix.config.platform }}${SUFFIX} - env: - SUFFIX: ${{ matrix.config.platform == 'windows-x86_64' && '.dll' || (matrix.config.platform == 'macos-arm64' && '.dylib' || '.so') }} + cp ${{ matrix.config.artifact }} dist/virtualjaguar_libretro-${{ matrix.config.platform }}$(echo "${{ matrix.config.artifact }}" | grep -o '\.[^.]*$') - name: Upload artifact uses: actions/upload-artifact@v4 @@ -91,7 +172,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | TAG="${GITHUB_REF_NAME}" - # Collect all built binaries + echo "==> Creating release ${TAG} with artifacts:" find artifacts/ -type f | sort gh release create "${TAG}" \ diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 00000000..7272307d --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,77 @@ +name: Bump Version & Release + +on: + workflow_dispatch: + inputs: + bump: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + dry_run: + description: 'Dry run (no commit/tag/push)' + required: false + type: boolean + default: false + +permissions: + contents: write + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Parse current version + id: current + run: | + VER=$(grep -oP 'library_version\s*=\s*"v\K[0-9]+\.[0-9]+\.[0-9]+' libretro.c) + echo "version=${VER}" >> "$GITHUB_OUTPUT" + echo "Current version: v${VER}" + + - name: Compute next version + id: next + run: | + IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.current.outputs.version }}" + case "${{ inputs.bump }}" in + major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR + 1)); PATCH=0 ;; + patch) PATCH=$((PATCH + 1)) ;; + esac + NEXT="${MAJOR}.${MINOR}.${PATCH}" + echo "version=${NEXT}" >> "$GITHUB_OUTPUT" + echo "Next version: v${NEXT}" + + - name: Update libretro.c + run: | + sed -i 's/library_version = "v${{ steps.current.outputs.version }}"/library_version = "v${{ steps.next.outputs.version }}"/' libretro.c + grep 'library_version' libretro.c + + - name: Commit, tag, and push + if: ${{ !inputs.dry_run }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add libretro.c + git commit -m "Bump version to v${{ steps.next.outputs.version }}" + git tag "v${{ steps.next.outputs.version }}" + git push origin HEAD --tags + + - name: Summary + run: | + echo "### Version Bump" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "- **From:** v${{ steps.current.outputs.version }}" >> "$GITHUB_STEP_SUMMARY" + echo "- **To:** v${{ steps.next.outputs.version }}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Bump:** ${{ inputs.bump }}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Dry run:** ${{ inputs.dry_run }}" >> "$GITHUB_STEP_SUMMARY" + if [ "${{ inputs.dry_run }}" = "false" ]; then + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Tag \`v${{ steps.next.outputs.version }}\` pushed — Release workflow will create the GitHub Release automatically." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.gitignore b/.gitignore index 2c9fbe2f..c528f840 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Build artifacts *.o *.so *.dll @@ -5,7 +6,43 @@ *.exe *.js *.bc + +# macOS .DS_Store .build + +# IDE / editor /.claude +/.cursor +/.vscode + +# Python virtual environments +.venv*/ + +# Test binaries and debug artifacts test/test_blitter_simd +test/test_blitter_scalar +test/test_* +!test/test_*.c +!test/test_*.sh +!test/test_*.py +test/dump_caller +test/dump_pc +test/heap_search +test/*.dSYM/ + +# Test logs +*.log +test/*.log + +# Test ROMs (keep source scripts, ignore binaries) +test/roms/*.jag +test/roms/*.rom +test/roms/private/ + +# Reference docs (large PDFs, not source) +docs/atari-jaguar-1999/ + +# LLDB scripts (local debug helpers) +test/lldb_*.cmd +test/lldb_*.py diff --git a/Makefile.common b/Makefile.common index 9586f304..d9623b9b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -92,24 +92,14 @@ endif ifneq (,$(filter MINGW64% MINGW32%,$(MSYSTEM))) BLITTER_SIMD_SRC := $(CORE_DIR)/src/blitter_simd_sse2.c endif -# 32-bit x86 needs explicit -msse2 (x86_64 has it baseline). -# Skip for MSVC (cl.exe) — it enables SSE2 by default on x86 and does -# not understand the GCC -msse2 flag. -ifeq ($(BLITTER_SIMD_SRC),$(CORE_DIR)/src/blitter_simd_sse2.c) -ifeq (,$(findstring msvc,$(platform))) -ifneq (,$(filter i686 i386 x86 win32,$(ARCH) $(platform))) - CFLAGS += -msse2 -endif -ifneq (,$(filter MINGW32%,$(MSYSTEM))) - CFLAGS += -msse2 -endif -endif -endif # Native build fallback: auto-detect from host architecture, but only for # native-build platforms (unix/osx/win). Cross-compile targets (vita, ps3, # libnx, etc.) set platform explicitly and should not use host detection. +# Also skip when CC looks like a cross-compiler (e.g. arm-webos-linux-gnueabi-gcc). +BLITTER_CROSS_CC := $(findstring arm-,$(CC))$(findstring aarch64-,$(CC))$(findstring mips,$(CC))$(findstring powerpc,$(CC)) ifeq ($(BLITTER_SIMD_SRC),) +ifeq ($(BLITTER_CROSS_CC),) ifneq (,$(filter unix osx win,$(platform))) ifneq (,$(filter x86_64 i686 i386,$(shell uname -m 2>/dev/null))) BLITTER_SIMD_SRC := $(CORE_DIR)/src/blitter_simd_sse2.c @@ -120,6 +110,7 @@ endif endif endif endif +endif # Fall back to scalar if no SIMD was selected (e.g., exotic platforms) ifeq ($(BLITTER_SIMD_SRC),) @@ -128,6 +119,14 @@ endif SOURCES_C += $(BLITTER_SIMD_SRC) +# Add -msse2 flag for all GCC/Clang SSE2 builds. No-op on x86_64 (baseline), +# required on i686 / gcc -m32. Skip for MSVC (uses /arch:SSE2, not -msse2). +ifeq ($(BLITTER_SIMD_SRC),$(CORE_DIR)/src/blitter_simd_sse2.c) +ifeq (,$(findstring msvc,$(platform))) + CFLAGS += -msse2 +endif +endif + ifneq ($(STATIC_LINKING), 1) SOURCES_C += \ $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ diff --git a/jni/Android.mk b/jni/Android.mk index aaaf6af5..cfc3b966 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -1,6 +1,6 @@ -LOCAL_PATH := $(call my-dir) +LOCAL_PATH := $(call my-dir)/.. -CORE_DIR := $(LOCAL_PATH)/.. +CORE_DIR := $(LOCAL_PATH) include $(CORE_DIR)/Makefile.common diff --git a/libretro.c b/libretro.c index 2a1a613a..a12b1a3d 100644 --- a/libretro.c +++ b/libretro.c @@ -781,6 +781,8 @@ bool retro_serialize(void *data, size_t size) uint8_t *buf, *start; size_t written; uint32_t magic, version, flags, reserved; + extern uint8_t jerry_ram_8[]; + extern bool lowerField; if (!data || size < STATE_SIZE) return false; @@ -802,11 +804,9 @@ bool retro_serialize(void *data, size_t size) STATE_SAVE_BUF(buf, jaguarMainRAM, 0x200000); /* 2 MB main RAM */ STATE_SAVE_BUF(buf, tomRam8, 0x4000); /* 16 KB TOM registers */ - extern uint8_t jerry_ram_8[]; STATE_SAVE_BUF(buf, jerry_ram_8, 0x10000); /* 64 KB JERRY registers */ /* Jaguar misc state */ - extern bool lowerField; STATE_SAVE_VAR(buf, lowerField); /* Module state */ @@ -838,6 +838,8 @@ bool retro_unserialize(const void *data, size_t size) { const uint8_t *buf; uint32_t magic, version, flags, reserved; + extern uint8_t jerry_ram_8[]; + extern bool lowerField; if (!data || size < STATE_SIZE) return false; @@ -857,11 +859,9 @@ bool retro_unserialize(const void *data, size_t size) STATE_LOAD_BUF(buf, jaguarMainRAM, 0x200000); STATE_LOAD_BUF(buf, tomRam8, 0x4000); - extern uint8_t jerry_ram_8[]; STATE_LOAD_BUF(buf, jerry_ram_8, 0x10000); /* Jaguar misc state */ - extern bool lowerField; STATE_LOAD_VAR(buf, lowerField); /* Module state */ diff --git a/src/blitter_simd_sse2.c b/src/blitter_simd_sse2.c index c423b794..67dc9dd8 100644 --- a/src/blitter_simd_sse2.c +++ b/src/blitter_simd_sse2.c @@ -10,6 +10,17 @@ #include "blitter_simd.h" #include /* SSE2 */ +#include /* memcpy for type-punning extract */ + +/* _mm_cvtsi128_si64 only exists on x86_64 (needs 64-bit GP register). + * memcpy from the __m128i is portable, alignment-safe, and compilers + * optimize it to a single register move. */ +static uint64_t sse2_extract_u64(__m128i v) +{ + uint64_t r; + memcpy(&r, &v, sizeof(r)); + return r; +} /* Logic Function Unit — SSE2 * @@ -41,7 +52,7 @@ static uint64_t sse2_lfu(uint64_t srcd, uint64_t dstd, uint8_t lfu_func) __m128i t3 = _mm_and_si128(_mm_and_si128(vs, vd), vf3); __m128i result = _mm_or_si128(_mm_or_si128(t0, t1), _mm_or_si128(t2, t3)); - return (uint64_t)_mm_cvtsi128_si64(result); + return sse2_extract_u64(result); } /* Data Comparator — SSE2 @@ -75,6 +86,7 @@ static uint8_t sse2_dcomp(uint64_t patd, uint64_t srcd, uint64_t dstd, bool cmpd static uint8_t sse2_zcomp(uint64_t srcz, uint64_t dstz, uint8_t zmode) { uint8_t result = 0; + uint8_t packed = 0; __m128i vs = _mm_set_epi64x(0, (int64_t)srcz); __m128i vd = _mm_set_epi64x(0, (int64_t)dstz); @@ -107,7 +119,6 @@ static uint8_t sse2_zcomp(uint64_t srcz, uint64_t dstz, uint8_t zmode) /* movemask gives 2 bits per 16-bit lane (one per byte). * Convert to 1 bit per lane: lanes at positions 0,2,4,6 */ - uint8_t packed = 0; if (result & 0x03) packed |= 0x01; /* lane 0: bytes 0-1 */ if (result & 0x0C) packed |= 0x02; /* lane 1: bytes 2-3 */ if (result & 0x30) packed |= 0x04; /* lane 2: bytes 4-5 */ @@ -129,6 +140,8 @@ static uint64_t sse2_byte_merge(uint64_t src, uint64_t dst, uint16_t mask) * Bytes 1-7 = 0xFF or 0x00 from mask bits 8-14 (whole-byte select). * We expand each bit to a full 0xFF byte using sign-extension. */ uint64_t sel64 = (uint64_t)(mask & 0xFF); /* byte 0: per-bit */ + __m128i vmask, vsrc, vdst, r; + sel64 |= (uint64_t)((uint8_t)(-(int8_t)((mask >> 8) & 1))) << 8; sel64 |= (uint64_t)((uint8_t)(-(int8_t)((mask >> 9) & 1))) << 16; sel64 |= (uint64_t)((uint8_t)(-(int8_t)((mask >> 10) & 1))) << 24; @@ -137,17 +150,17 @@ static uint64_t sse2_byte_merge(uint64_t src, uint64_t dst, uint16_t mask) sel64 |= (uint64_t)((uint8_t)(-(int8_t)((mask >> 13) & 1))) << 48; sel64 |= (uint64_t)((uint8_t)(-(int8_t)((mask >> 14) & 1))) << 56; - __m128i vmask = _mm_set_epi64x(0, (int64_t)sel64); - __m128i vsrc = _mm_set_epi64x(0, (int64_t)src); - __m128i vdst = _mm_set_epi64x(0, (int64_t)dst); + vmask = _mm_set_epi64x(0, (int64_t)sel64); + vsrc = _mm_set_epi64x(0, (int64_t)src); + vdst = _mm_set_epi64x(0, (int64_t)dst); /* result = (src & mask) | (dst & ~mask) */ - __m128i r = _mm_or_si128( + r = _mm_or_si128( _mm_and_si128(vsrc, vmask), _mm_andnot_si128(vmask, vdst) ); - return (uint64_t)_mm_cvtsi128_si64(r); + return sse2_extract_u64(r); } const blitter_simd_ops_t blitter_simd_ops = { diff --git a/src/dsp.c b/src/dsp.c index 9380e13a..013b86f2 100644 --- a/src/dsp.c +++ b/src/dsp.c @@ -2518,6 +2518,7 @@ INLINE static void DSP_xor(void) size_t DSPStateSave(uint8_t *buf) { uint8_t *start = buf; + uint8_t active_bank; STATE_SAVE_BUF(buf, dsp_ram_8, sizeof(dsp_ram_8)); STATE_SAVE_VAR(buf, dsp_pc); @@ -2536,7 +2537,7 @@ size_t DSPStateSave(uint8_t *buf) STATE_SAVE_BUF(buf, dsp_reg_bank_0, sizeof(dsp_reg_bank_0)); STATE_SAVE_BUF(buf, dsp_reg_bank_1, sizeof(dsp_reg_bank_1)); - uint8_t active_bank = (dsp_reg == dsp_reg_bank_0) ? 0 : 1; + active_bank = (dsp_reg == dsp_reg_bank_0) ? 0 : 1; STATE_SAVE_VAR(buf, active_bank); STATE_SAVE_VAR(buf, dsp_opcode_first_parameter); @@ -2562,6 +2563,7 @@ size_t DSPStateSave(uint8_t *buf) size_t DSPStateLoad(const uint8_t *buf) { const uint8_t *start = buf; + uint8_t active_bank; STATE_LOAD_BUF(buf, dsp_ram_8, sizeof(dsp_ram_8)); STATE_LOAD_VAR(buf, dsp_pc); @@ -2580,7 +2582,6 @@ size_t DSPStateLoad(const uint8_t *buf) STATE_LOAD_BUF(buf, dsp_reg_bank_0, sizeof(dsp_reg_bank_0)); STATE_LOAD_BUF(buf, dsp_reg_bank_1, sizeof(dsp_reg_bank_1)); - uint8_t active_bank; STATE_LOAD_VAR(buf, active_bank); if (active_bank == 0) { From 8e63cd12d33b9112b9b07df53e078b6a7b839691 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 22 Apr 2026 22:46:24 -0400 Subject: [PATCH 2/3] Fix C89 mixed declarations in gpu.c state save/load Move uint8_t active_bank declarations to function scope in GPUStateSave and GPUStateLoad to satisfy C89/MSVC. Made-with: Cursor --- src/gpu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gpu.c b/src/gpu.c index 3c477d24..9d43ec46 100644 --- a/src/gpu.c +++ b/src/gpu.c @@ -1700,6 +1700,7 @@ INLINE static void gpu_opcode_sh(void) size_t GPUStateSave(uint8_t *buf) { uint8_t *start = buf; + uint8_t active_bank; STATE_SAVE_BUF(buf, gpu_ram_8, sizeof(gpu_ram_8)); STATE_SAVE_VAR(buf, gpu_pc); @@ -1719,7 +1720,7 @@ size_t GPUStateSave(uint8_t *buf) STATE_SAVE_BUF(buf, gpu_reg_bank_1, sizeof(gpu_reg_bank_1)); /* Save which register bank is active (0 or 1) */ - uint8_t active_bank = (gpu_reg == gpu_reg_bank_0) ? 0 : 1; + active_bank = (gpu_reg == gpu_reg_bank_0) ? 0 : 1; STATE_SAVE_VAR(buf, active_bank); STATE_SAVE_VAR(buf, gpu_instruction); @@ -1735,6 +1736,7 @@ size_t GPUStateSave(uint8_t *buf) size_t GPUStateLoad(const uint8_t *buf) { const uint8_t *start = buf; + uint8_t active_bank; STATE_LOAD_BUF(buf, gpu_ram_8, sizeof(gpu_ram_8)); STATE_LOAD_VAR(buf, gpu_pc); @@ -1754,7 +1756,6 @@ size_t GPUStateLoad(const uint8_t *buf) STATE_LOAD_BUF(buf, gpu_reg_bank_1, sizeof(gpu_reg_bank_1)); /* Restore register bank pointers */ - uint8_t active_bank; STATE_LOAD_VAR(buf, active_bank); if (active_bank == 0) { From 2c20be727bfadbcbdd2d06164eb07f9735ffd8bb Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 22 Apr 2026 22:47:38 -0400 Subject: [PATCH 3/3] Update CLAUDE.md with C89 rules, testing, and CD docs Add C Language Standard section documenting the C89/GNU89 requirement and local lint command. Update stale "no test suite" line, add Jaguar CD, Testing, and expanded Key Directories sections. Made-with: Cursor --- CLAUDE.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 385a9acb..00cf20ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,10 +20,28 @@ Output binary name varies by platform: - Linux: `virtualjaguar_libretro.so` - Windows: `virtualjaguar_libretro.dll` -There is no test suite. CI runs `make -j4` on Ubuntu (GCC) and macOS (Clang). +CI runs `make -j4` on Ubuntu (GCC) and macOS (Clang), plus screenshot regression tests via `test/regression_test.sh`. See `docs/test-infrastructure.md` for the full test harness inventory. ## Architecture +### C Language Standard — C89/GNU89 + +This codebase **must** compile as C89 (GNU89 dialect). The libretro buildbot uses MSVC on Windows, which enforces C89 strictly. CI includes a `c89-lint` job that catches violations. + +**Rules:** +- **No mid-block variable declarations.** All variables must be declared at the top of their enclosing block (function or `{}`), before any statements. This is the most common violation. +- `//` comments are allowed (GNU89 extension), but `/* */` is preferred for new code. +- No C99 features: no `for (int i = ...)`, no compound literals, no designated initializers, no VLAs. +- SIMD files (`src/blitter_simd_sse2.c`, `src/blitter_simd_neon.c`) are exempt from the lint check since they require platform-specific headers. +- Machine-generated files (`src/m68000/*`) are also exempt. + +**Local check before pushing:** +```bash +gcc -fsyntax-only -std=gnu89 -Werror=declaration-after-statement \ + -I. -Isrc -Isrc/m68000 -Ilibretro-common/include \ + -D__LIBRETRO__ -DINLINE="inline" src/YOURFILE.c +``` + ### Atari Jaguar Hardware Emulation The Jaguar has four processors sharing a unified memory-mapped address space: @@ -56,12 +74,30 @@ Core options defined in `libretro_core_options.h` control blitter mode, BIOS usa - `src/` — emulator core (hardware chips, CPU, I/O, BIOS ROMs as C arrays) - `src/m68000/` — UAE-derived 68K CPU emulation - `libretro-common/` — shared libretro utility library (string, file, VFS) -- `docs/` — original Virtual Jaguar documentation, changelog, known issues +- `docs/` — documentation: changelog, known issues, BUTCH register map, CD data flow, test infrastructure +- `test/tools` — test scripts and headless front-ends +- `test/roms` — test ROMs; `private/` subdirectory has commercial ROMs and BIOSes ### Build System `Makefile` handles 30+ platform targets with auto-detection. `Makefile.common` lists all source files. Platform is selected via `platform=` variable or auto-detected from `uname`. Key flags: `-D__LIBRETRO__`, `-DMSB_FIRST` for big-endian platforms. +### Jaguar CD Emulation + +CD support is implemented across `src/cdrom.c` (BUTCH chip / FIFO / DSA commands), `src/cdintf.c` (disc image loading: CUE/BIN, CHD, CDI), and hooks in `src/jaguar.c` (BIOS auth bypass, boot stub injection). + +Key docs: +- `docs/butch-registers.md` — full BUTCH register map ($DFFF00-$DFFF2F) with bit definitions +- `docs/cd-data-flow.md` — how CD data moves from disc to RAM (I2S -> FIFO -> GPU ISR -> RAM), BIOS code map, boot stub layout + +### Testing + +See `docs/test-infrastructure.md` for all test harnesses: +- `test/headless.py` — Python headless runner via libretro.py (screenshots, frame control) +- `test/regression_test.sh` — screenshot regression suite with baseline comparison +- `test/test_cd_boot.c` — low-level C harness with dlsym access to 68K registers and RAM +- `test/sram_test.sh` — SRAM interface round-trip testing + ### Known Limitations - Blitter not fully cycle-accurate (some games need fast blitter mode)