Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .github/workflows/build-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ env:

jobs:
build:
continue-on-error: true
strategy:
fail-fast: false
matrix:
arch:
- runner: ubuntu-22.04
- runner: ubuntu-24.04
name: x86_64
- runner: ubuntu-22.04-arm
- runner: ubuntu-24.04-arm
name: aarch64

name: ${{ matrix.arch.name }}
Expand All @@ -35,10 +35,13 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
qt6-{base,base-private,multimedia}-dev qt6-wayland libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 libfaad-dev
sudo apt install --allow-downgrades g++-14 ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
qt6-{base,base-private,multimedia}-dev qt6-wayland libqt6svg6-dev libarchive-dev libzstd-dev libfuse2t64 libfaad-dev python3-venv
python3 -m venv "$RUNNER_TEMP/cmake-env"
"$RUNNER_TEMP/cmake-env/bin/pip" install cmake==3.31.6
echo "$RUNNER_TEMP/cmake-env/bin" >> "$GITHUB_PATH"
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON
run: cmake -B build -G Ninja -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON
- name: Build
run: |
cmake --build build
Expand Down
79 changes: 79 additions & 0 deletions .github/workflows/core-regressions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Core regressions

on:
push:
branches:
- master
- fix/core-regressions-20260907
- modernize/cpp26-bitfield
pull_request:
branches:
- master

permissions:
contents: read

jobs:
regression:
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: GCC 14
compiler: g++-14
flags: ""
- name: Clang 18 ASan UBSan
compiler: clang++-18
flags: -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install C++26 toolchains
run: |
sudo apt-get update
sudo apt-get install -y g++-14 clang-18
python3 -m venv "$RUNNER_TEMP/cmake-env"
"$RUNNER_TEMP/cmake-env/bin/pip" install cmake==3.31.6
echo "$RUNNER_TEMP/cmake-env/bin" >> "$GITHUB_PATH"
- name: Configure
run: >-
cmake -S tests -B build-regression -DCMAKE_BUILD_TYPE=Debug
-DCMAKE_CXX_COMPILER="${{ matrix.compiler }}"
-DCMAKE_CXX_FLAGS="${{ matrix.flags }}"
-DCMAKE_EXE_LINKER_FLAGS="${{ matrix.flags }}"
- name: Build
run: cmake --build build-regression --parallel 2
- name: Test
run: ctest --test-dir build-regression --output-on-failure

core-build:
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
jit: ["ON", "OFF"]
name: C++26 core JIT ${{ matrix.jit }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install toolchain
run: |
sudo apt-get update
sudo apt-get install -y g++-14 ninja-build
python3 -m venv "$RUNNER_TEMP/cmake-env"
"$RUNNER_TEMP/cmake-env/bin/pip" install cmake==3.31.6
echo "$RUNNER_TEMP/cmake-env/bin" >> "$GITHUB_PATH"
- name: Configure full core
run: >-
cmake -S . -B build-core -G Ninja -DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
-DBUILD_QT_SDL=OFF -DENABLE_OGLRENDERER=OFF
-DENABLE_JIT=${{ matrix.jit }}
- name: Compile full core
run: cmake --build build-core --parallel 2
38 changes: 31 additions & 7 deletions BUILD.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Building melonDS

## Toolchain requirements for this fork

Use **CMake 3.30 or newer** and a compiler supporting **C++26 mode** (GCC 14+
or Clang 17+). The build does not silently fall back to C++17/20/23. Language
mode support is not a promise that all C++26 features are available; see
[the migration notes](docs/Cpp26.md) for scope and verification boundaries.
Vendored libraries keep their own language requirements.

For Ubuntu 24.04, install `g++-14 python3-venv`, install CMake 3.31.6 in a
virtual environment, activate it, and select `-DCMAKE_C_COMPILER=gcc-14
-DCMAKE_CXX_COMPILER=g++-14` when configuring. For example:

```sh
python3 -m venv .venv-build
. .venv-build/bin/activate
python -m pip install cmake==3.31.6
cmake -B build -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
```

The dependency lists below do not upgrade an old system compiler. Older
Ubuntu/BSD/Nix environments may need a newer toolchain separately. Linux CI
now builds on Ubuntu 24.04; do not assume its binaries retain Ubuntu 22.04
runtime compatibility. Use a new build directory when switching compilers.

* [Linux](#linux)
* [Windows](#windows)
* [macOS](#macos)
Expand All @@ -9,17 +33,17 @@
* Ubuntu:
* All versions: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libarchive-dev libenet-dev libzstd-dev libfaad-dev`
* 24.04: `sudo apt install qt6-{base,base-private,multimedia,svg}-dev`
* 22.04: `sudo apt install qtbase6-dev qtbase6-private-dev qtmultimedia6-dev libqt6svg6-dev`
* 22.04: `sudo apt install qt6-base-dev qt6-base-private-dev qt6-multimedia-dev libqt6svg6-dev`
* Older versions: `sudo apt install qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev`
Also add `-DUSE_QT6=OFF` to the first CMake command below.
* Fedora: `sudo dnf install gcc-c++ cmake extra-cmake-modules SDL2-devel libarchive-devel enet-devel libzstd-devel faad2-devel qt6-{qtbase,qtbase-private,qtmultimedia,qtsvg}-devel wayland-devel`
* Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt6-{base,multimedia,svg} libarchive enet zstd faad2`
2. Download the melonDS repository and prepare:
```bash
git clone https://github.com/melonDS-emu/melonDS
git clone https://github.com/gajae1/melonDS
cd melonDS
```
3. Compile:
3. Compile (select the newer compiler above where necessary):
```bash
cmake -B build
cmake --build build -j$(nproc --all)
Expand All @@ -34,7 +58,7 @@
4. Install git and clone the repository
```bash
pacman -S git
git clone https://github.com/melonDS-emu/melonDS
git clone https://github.com/gajae1/melonDS
cd melonDS
```
5. Install dependencies:
Expand All @@ -44,7 +68,7 @@
```
6. Install Qt and configure the build directory
* Dynamic builds (with DLLs)
1. Install Qt: `pacman -S <prefix>-{qt6-base,qt6-svg,qt6-multimedia,qt6-svg,qt6-tools}`
1. Install Qt: `pacman -S <prefix>-{qt6-base,qt6-svg,qt6-multimedia,qt6-tools}`
2. Set up the build directory with `cmake -B build`
* Static builds (without DLLs, standalone executable)
1. Install Qt: `pacman -S <prefix>-qt5-static`
Expand All @@ -59,7 +83,7 @@ If everything went well, melonDS should now be in the `build` folder. For dynami
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd faad2`
3. Download the melonDS repository and prepare:
```zsh
git clone https://github.com/melonDS-emu/melonDS
git clone https://github.com/gajae1/melonDS
cd melonDS
```
4. Compile:
Expand All @@ -77,5 +101,5 @@ If you want an app bundle that can be distributed to other computers without nee

melonDS provides a Nix flake with support for both macOS and Linux. The [Nix package manager](https://nixos.org) needs to be installed to use it.

* To run melonDS, just type `nix run github:melonDS-emu/melonDS`.
* To run melonDS, just type `nix run github:gajae1/melonDS`.
* To get a shell for development, clone the melonDS repository and type `nix develop` in its directory.
8 changes: 6 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.30)

cmake_policy(VERSION 3.15)
if (POLICY CMP0076)
Expand All @@ -22,6 +22,10 @@ project(melonDS
HOMEPAGE_URL "https://melonds.kuribo64.net"
LANGUAGES C CXX)

if (NOT "cxx_std_26" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
message(FATAL_ERROR "melonDS requires a compiler with C++26 mode (GCC 14+ or Clang 17+). No older-standard fallback is used.")
endif()

include(CheckSymbolExists)
include(CheckLibraryExists)
include(CMakeDependentOption)
Expand All @@ -32,7 +36,7 @@ include(Sanitizers)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

Expand Down
117 changes: 117 additions & 0 deletions docs/Cpp26.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# C++26 migration and accuracy-preserving optimization

## Scope

Project-owned C++ targets now require **C++26 mode** and **CMake 3.30+**.
The core exports `cxx_std_26` to consumers; the former `src/CMakeLists.txt`
C++17 override is removed. The Qt frontend, network utilities and regression
suite inherit the new mode. Vendored projects retain their own requirements
(for example Teakra still selects C++17); C sources remain C11.

C++26 mode does not mean every C++26 language/library feature is implemented.
GCC 14+ and Clang 17+ are candidate toolchains for this mode, not a claim that
every platform combination was tested. The Linux CI explicitly uses GCC 14
and Clang 18, and CMake 3.31.6. The application workflows remain responsible for
platform build coverage. Unsupported toolchains fail rather than falling back.

The first library modernization uses the established `<bit>` operations
`std::countr_zero` / `std::countl_zero` (introduced in C++20), not experimental
reflection, contracts or a new SIMD API. Merely selecting C++26 is not claimed
to improve performance.

## Changes to invalidation bitfields

`NonStupidBitfield.h` iterates memory-invalidation bits used by GPU VRAM
tracking. Clear the lowest set bit with `x &= x - 1`, removing the dependency
on constructing a shifted mask from the counted bit index. Use standard bit
operations instead of compiler-specific builtins.

Two independently reproduced edge cases are fixed:

- Empty `SetRange` / `CheckRange` operations return before a shift by 64 or a
possible access past the final word (an empty range may start at `Size`).
- Iterator termination checks the absolute bit index, not just the bit index
within a 64-bit word. Padding bits in a partial final word are not returned.

Storage layout, guest timing, CPU instructions, DMA behavior, JIT emitters and
savestate format are unchanged. No game-compatibility improvement is claimed
from these edge cases without an observed game reproduction.

## Verification

The baseline header `d471f0afdd31c495c1d154a6b2acc0e8e14a5e45` was retrieved
from GitHub and its Git blob hash was checked before local testing.
The baseline passed valid nonempty-range tests, failed the padding test, and
UBSan rejected a 64-bit shift in the empty-range test. The modified header
passed the tests with GCC 14.2 and Clang 17, including ASan/UBSan.

The committed suite adds a C++26-mode compile assertion and the bitfield tests
to the seven existing CTest entries. It also compiles the complete core with
JIT enabled and disabled in CI. A compiled core is not an executed JIT test;
record per-job CI results separately. Local testing used a source fixture,
not a full application checkout. Real GPU/game/hardware tests were not run.

## Measured microbenchmark

Same harness and **same C++26 flags for both implementations**:
`-std=c++2c -O3 -fwrapv`, no `-march=native`, no added AVX requirement, no LTO.
AMD EPYC 9V74 virtual host, one allowed logical CPU, deterministic inputs,
one warmup per variant, nine randomized-order paired samples. Report medians;
checksums matched for every pair. Each sample scans 128 1024-bit fields for
1024 iterations. Inputs are mutated each iteration to prevent loop hoisting;
therefore the case named `empty` starts empty but is not permanently empty.

| Compiler | Initial pattern | Before (ms) | After (ms) | Time reduction |
| --- | --- | ---: | ---: | ---: |
| GCC 14.2 | empty | 0.979 | 0.836 | 14.65% |
| GCC 14.2 | sparse | 2.965 | 2.192 | 26.06% |
| GCC 14.2 | mixed | 71.412 | 43.396 | 39.23% |
| GCC 14.2 | dense | 141.530 | 82.109 | 41.98% |
| Clang 17 | empty | 0.863 | 0.873 | -1.17% |
| Clang 17 | sparse | 2.148 | 1.843 | 14.19% |
| Clang 17 | mixed | 52.214 | 43.839 | 16.04% |
| Clang 17 | dense | 110.353 | 91.910 | 16.71% |

These are **routine times, not emulator FPS**. Results are environment- and
compiler-dependent, and a shared VM is not a dedicated performance lab.
Do not turn these numbers into fixed CI timing thresholds.

To compare on another machine, build `tests/BitfieldBenchmark.cpp` against
before/after source directories with the same compiler/flags, alternate runs
for `empty`, `sparse`, `mixed`, `dense`, and verify equal checksums. The current
version can also be built with:

```sh
cmake -S tests -B build-bench -DCMAKE_BUILD_TYPE=Release -DMELONDS_BUILD_MICROBENCHMARKS=ON
cmake --build build-bench --target BitfieldBenchmark
./build-bench/BitfieldBenchmark mixed
```

## Acceptance policy for subsequent emulation work

A hardware-accuracy change needs a reproducible test ROM/input and an observed
DS/DSi result (record console revision, firmware/boot mode and test hash).
Agreement with another emulator is supporting evidence, not the hardware oracle.
A test passing on the current interpreter alone does not prove accurate timing.

An optimization must preserve guest-visible results: ordering of events,
integer rounding, memory aliases, IO side effects, DMA/IRQ behavior, and state
restoration. Compare interpreter/JIT and relevant renderers, with exact
frame/audio/state comparisons where meaningful; do not blanket-hash structures
containing pointers, padding or wall-clock-dependent data.

Measure matched workloads with median and tail frame time. Keep host throughput
separate from the speed of the emulated hardware. Do not skip guest work,
change timing constants, enable fast-math, or add unchecked assumptions solely
to improve a benchmark. SIMD/assembly stays optional and evidence-driven.

The next hardware-oriented candidates remain FreeBIOS unsigned square root
(including generated BIOS data), DSi direct-boot state, and rendering/capture
regressions. Those are not silently included in this migration.

## References

- https://gcc.gnu.org/projects/cxx-status.html
- https://clang.llvm.org/cxx_status.html
- https://cmake.org/cmake/help/latest/release/3.30.html
- https://libcxx.llvm.org/Status/Cxx26.html
Loading
Loading