Skip to content

Commit 2d7bef7

Browse files
authored
Merge branch 'master' into master
2 parents c32eac4 + 8f6d51b commit 2d7bef7

241 files changed

Lines changed: 36950 additions & 6954 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/Linux-asan-ubsan.yml

Lines changed: 374 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/Linux-libretro-common-samples.yml

Lines changed: 285 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,26 @@ jobs:
2525
- name: Install dependencies
2626
run: |
2727
sudo apt-get update -y
28-
sudo apt-get install -y build-essential zlib1g-dev
28+
sudo apt-get install -y build-essential zlib1g-dev clang
2929
3030
- name: Checkout
3131
uses: actions/checkout@v3
3232

3333
- name: Build and run samples
3434
shell: bash
3535
working-directory: libretro-common/samples
36+
env:
37+
# Build samples under AddressSanitizer + UndefinedBehaviorSanitizer.
38+
# Most overflow tests under libretro-common/samples (rpng_chunk_
39+
# overflow_test, rbmp_test, vfs_read_overflow_test, cdrom_cuesheet
40+
# _overflow_test, cdfs_dir_record_test, chd_meta_overflow_test, ...)
41+
# use ASan as the regression discriminator for the bound-check
42+
# they verify, so make ASan the default for this workflow. Each
43+
# test's Makefile honours SANITIZER= via the conventional opt-in
44+
# block (matching samples/tasks/http/Makefile). Test sources
45+
# that don't have the opt-in block treat the variable as
46+
# ignored.
47+
MAKE_ARGS: "SANITIZER=address,undefined"
3648
run: |
3749
set -u
3850
set -o pipefail
@@ -48,18 +60,29 @@ jobs:
4860
archive_zstd_test
4961
config_file_test
5062
path_resolve_realpath_test
63+
fill_pathname_test
5164
nbio_test
5265
rpng
5366
rzip_chunk_size_test
5467
net_ifinfo
5568
vfs_read_overflow_test
5669
cdrom_cuesheet_overflow_test
70+
cdfs_dir_record_test
71+
chd_meta_overflow_test
72+
strlcpy_append_test
5773
http_parse_test
5874
rjson_test
5975
rtga_test
6076
rbmp_test
6177
rpng_chunk_overflow_test
6278
rpng_roundtrip_test
79+
word_wrap_overflow_test
80+
task_queue_title_error_test
81+
tpool_wait_test
82+
retro_atomic_test
83+
retro_atomic_extern_c_linkage_test
84+
retro_atomic_extern_c_linkage_test_cxx
85+
retro_spsc_test
6386
)
6487
6588
# Per-binary run command (overrides ./<binary> if present).
@@ -114,7 +137,7 @@ jobs:
114137
fi
115138
116139
# Build
117-
if ! ( cd "$d" && make clean all ); then
140+
if ! ( cd "$d" && make clean all $MAKE_ARGS ); then
118141
printf '\n::error title=Build failed::%s failed to build\n' "$rel"
119142
fails=$((fails+1))
120143
continue
@@ -177,3 +200,263 @@ jobs:
177200
if [[ $fails -gt 0 ]]; then
178201
exit 1
179202
fi
203+
204+
- name: Compile-test retro_atomic.h from a C++11 TU
205+
shell: bash
206+
working-directory: libretro-common
207+
run: |
208+
# The C++11 backend in retro_atomic.h is a fresh code path that
209+
# none of the C samples above exercise. Compile a tiny inline
210+
# C++11 TU against the in-tree header to catch regressions like
211+
# accidentally re-introducing an extern "C" wrapper around the
212+
# std::atomic include, or breaking the __cplusplus / _MSVC_LANG
213+
# gate. This step is build-and-run, single-threaded only -- the
214+
# behavioural SPSC stress is already covered by the C test
215+
# binary above on this same host, and the C++11 backend bottoms
216+
# out through the same libstdc++ __atomic_* builtins.
217+
#
218+
# Two fixtures share the g++/clang++ x c++11/14/17 sweep:
219+
#
220+
# cxx_smoke.cpp : header included at C++ linkage,
221+
# the "ordinary" C++ TU shape.
222+
# cxx_smoke_extern_c.cpp : header included from inside an
223+
# extern "C" { ... } wrap, the
224+
# shape ui_qt.cpp uses under
225+
# !CXX_BUILD. This is the
226+
# regression case for the
227+
# RETRO_BEGIN_DECLS_CXX shield in
228+
# retro_atomic.h's C++11 backend.
229+
# The standalone build of the
230+
# sample at samples/atomic/
231+
# retro_atomic_extern_c_linkage/
232+
# covers the same case under the
233+
# default toolchain; this matrix
234+
# extends coverage to clang++ and
235+
# the higher language standards.
236+
set -u
237+
set -o pipefail
238+
239+
tmpdir=$(mktemp -d)
240+
cat > "$tmpdir/cxx_smoke.cpp" <<'EOF'
241+
#include <cstdio>
242+
#include <cstddef>
243+
#include <retro_atomic.h>
244+
245+
#if !defined(HAVE_RETRO_ATOMIC) || !defined(RETRO_ATOMIC_LOCK_FREE)
246+
# error "retro_atomic.h: capability flags not set on a C++11 host"
247+
#endif
248+
249+
int main(void) {
250+
retro_atomic_int_t ai; retro_atomic_int_init(&ai, 0);
251+
retro_atomic_size_t as; retro_atomic_size_init(&as, 0);
252+
253+
retro_atomic_store_release_int(&ai, 42);
254+
retro_atomic_store_release_size(&as, (std::size_t)42);
255+
256+
int li = retro_atomic_load_acquire_int(&ai);
257+
int ls = (int)retro_atomic_load_acquire_size(&as);
258+
259+
int pi = retro_atomic_fetch_add_int(&ai, 1);
260+
int ps = (int)retro_atomic_fetch_add_size(&as, 1);
261+
262+
retro_atomic_inc_int(&ai);
263+
retro_atomic_dec_size(&as);
264+
265+
int qi = retro_atomic_load_acquire_int(&ai);
266+
int qs = (int)retro_atomic_load_acquire_size(&as);
267+
268+
std::printf("backend: %s\n", RETRO_ATOMIC_BACKEND_NAME);
269+
270+
bool ok = (li == 42) && (ls == 42)
271+
&& (pi == 42) && (ps == 42)
272+
&& (qi == 44) && (qs == 42);
273+
std::puts(ok ? "ALL OK" : "FAIL");
274+
return ok ? 0 : 1;
275+
}
276+
EOF
277+
278+
# Regression fixture: include retro_atomic.h from inside an
279+
# extern "C" wrap. Faithful mirror of ui_qt.cpp's pattern.
280+
# Pre-fix retro_atomic.h would emit ~80 "template with C
281+
# linkage" errors here; the RETRO_BEGIN_DECLS_CXX shield
282+
# around <atomic> makes this compile cleanly.
283+
cat > "$tmpdir/cxx_smoke_extern_c.cpp" <<'EOF'
284+
#include <cstdio>
285+
#include <cstddef>
286+
287+
extern "C" {
288+
#include <retro_atomic.h>
289+
}
290+
291+
int main(void) {
292+
retro_atomic_int_t ai; retro_atomic_int_init(&ai, 0);
293+
retro_atomic_store_release_int(&ai, 7);
294+
int v = retro_atomic_load_acquire_int(&ai);
295+
std::printf("backend: %s\n", RETRO_ATOMIC_BACKEND_NAME);
296+
std::puts(v == 7 ? "ALL OK" : "FAIL");
297+
return v == 7 ? 0 : 1;
298+
}
299+
EOF
300+
301+
for cxx in g++ clang++; do
302+
for std in c++11 c++14 c++17; do
303+
for fixture in cxx_smoke cxx_smoke_extern_c; do
304+
echo "==> compile-test with $cxx -std=$std ($fixture)"
305+
$cxx -std=$std -Wall -Wextra -pedantic -O2 \
306+
-I include \
307+
"$tmpdir/$fixture.cpp" \
308+
-o "$tmpdir/$fixture" \
309+
|| { echo "::error title=C++ compile failed::$cxx -std=$std $fixture"; exit 1; }
310+
"$tmpdir/$fixture" \
311+
|| { echo "::error title=C++ smoke failed::$cxx -std=$std $fixture"; exit 1; }
312+
done
313+
done
314+
done
315+
316+
rm -rf "$tmpdir"
317+
318+
- name: Run retro_atomic_test under Clang + ThreadSanitizer
319+
shell: bash
320+
working-directory: libretro-common/samples/atomic/retro_atomic_test
321+
run: |
322+
# The native samples job above runs with GCC and ASan/UBSan.
323+
# Clang is the toolchain on every Apple platform, Android NDK
324+
# (since r18), Emscripten, and PS4-ORBIS, so a Clang lane is
325+
# not optional coverage. ThreadSanitizer is the strict
326+
# validator for this test in particular: it instruments every
327+
# atomic load and store and would flag a missing acquire /
328+
# release barrier as a race in the 1M-iteration SPSC stress
329+
# (a class of bug that x86 TSO would otherwise hide on the
330+
# native runner).
331+
set -u
332+
set -o pipefail
333+
334+
make clean
335+
CC=clang make all SANITIZER=thread
336+
337+
TSAN_OPTIONS=halt_on_error=1 ./retro_atomic_test
338+
339+
- name: Run retro_spsc_test under Clang + ThreadSanitizer
340+
shell: bash
341+
working-directory: libretro-common/samples/queues/retro_spsc_test
342+
run: |
343+
# retro_spsc.c is a lock-free SPSC byte queue built on
344+
# retro_atomic.h. Its correctness contract is acquire-load
345+
# / release-store on the head and tail cursors, with the
346+
# buffer reads/writes between them ordered by those barriers.
347+
# Missing or weakened barriers produce torn data on the
348+
# consumer side, observable as content mismatches in the
349+
# stress harness AND as TSan-reported races. The default
350+
# ASan/UBSan pass above catches the content mismatches but
351+
# not the races; this lane catches both.
352+
#
353+
# halt_on_error=1 makes TSan exit non-zero on the first race
354+
# rather than continuing -- which is what we want for CI:
355+
# any race here means the SPSC contract is broken.
356+
set -u
357+
set -o pipefail
358+
359+
make clean
360+
CC=clang make all SANITIZER=thread
361+
362+
TSAN_OPTIONS=halt_on_error=1 ./retro_spsc_test
363+
364+
# Cross-architecture validation lane for retro_atomic_test.
365+
#
366+
# The samples job above runs on x86_64, which is a strongly-ordered
367+
# (TSO) architecture. retro_atomic.h's contract is that acquire-load
368+
# / release-store / acq_rel-RMW emit real barriers on weakly-ordered
369+
# SMP targets (ARM, AArch64, PowerPC, MIPS). An x86_64 host run
370+
# cannot exercise that property, because TSO masks reordering bugs
371+
# at the hardware level even when the macros emit no barriers at all.
372+
#
373+
# This job cross-compiles retro_atomic_test for AArch64 and ARMv7 and
374+
# runs the binary under qemu-user-static. qemu-user emulates the
375+
# weak memory model faithfully enough to expose missing-barrier bugs
376+
# in the SPSC stress test, and is cheap enough to run on every push.
377+
#
378+
# We deliberately do NOT run the full samples sweep here -- the rest
379+
# of the samples don't have architecture-dependent codegen that
380+
# warrants the extra CI time. retro_atomic_test is the one that
381+
# benefits from cross-arch coverage.
382+
#
383+
# Real ARM hardware still beats qemu (see e.g. PostgreSQL's 2025
384+
# Win11/ARM64 atomic ordering bug, found only on real silicon),
385+
# but qemu catches most categorical errors and is much cheaper than
386+
# provisioning ARM runners.
387+
retro-atomic-cross:
388+
name: Cross-arch retro_atomic_test (${{ matrix.arch }})
389+
runs-on: ubuntu-latest
390+
timeout-minutes: 10
391+
strategy:
392+
fail-fast: false
393+
matrix:
394+
include:
395+
- arch: aarch64
396+
cc: aarch64-linux-gnu-gcc
397+
apt_pkgs: gcc-aarch64-linux-gnu
398+
qemu: qemu-aarch64-static
399+
sysroot: /usr/aarch64-linux-gnu
400+
- arch: armv7
401+
cc: arm-linux-gnueabihf-gcc
402+
apt_pkgs: gcc-arm-linux-gnueabihf
403+
qemu: qemu-arm-static
404+
sysroot: /usr/arm-linux-gnueabihf
405+
406+
steps:
407+
- name: Install dependencies
408+
run: |
409+
sudo apt-get update -y
410+
sudo apt-get install -y build-essential ${{ matrix.apt_pkgs }} qemu-user-static
411+
412+
- name: Checkout
413+
uses: actions/checkout@v3
414+
415+
- name: Build retro_atomic_test for ${{ matrix.arch }}
416+
working-directory: libretro-common/samples/atomic/retro_atomic_test
417+
run: |
418+
set -u
419+
set -o pipefail
420+
make clean
421+
CC=${{ matrix.cc }} make all
422+
423+
- name: Run retro_atomic_test under qemu-user
424+
working-directory: libretro-common/samples/atomic/retro_atomic_test
425+
run: |
426+
set -u
427+
set -o pipefail
428+
${{ matrix.qemu }} -L ${{ matrix.sysroot }} ./retro_atomic_test
429+
430+
- name: Inspect emitted atomic instructions
431+
working-directory: libretro-common/samples/atomic/retro_atomic_test
432+
run: |
433+
set -u
434+
set -o pipefail
435+
# Spot-check the codegen. If retro_atomic.h were silently
436+
# falling through to a no-barrier backend on this arch, the
437+
# asm would be conspicuously missing acquire/release
438+
# instructions. This is a cheap sanity check on top of the
439+
# behavioural SPSC test above.
440+
${{ matrix.cc }} -O2 -S \
441+
-I../../../include -DHAVE_THREADS \
442+
retro_atomic_test.c -o /tmp/retro_atomic_test.s
443+
echo
444+
echo '== Unique barrier-emitting mnemonics =='
445+
case "${{ matrix.arch }}" in
446+
aarch64)
447+
# Expect: ldar, stlr, and __aarch64_ldadd*_acq_rel libcalls
448+
# (or inline ldaddal LSE on +lse builds).
449+
pattern='\b(ldar|stlr|ldax|stlx|dmb|ldadd[a-z0-9_]*|swp[a-z0-9_]*|__aarch64_(ldadd|swp)[a-z0-9_]*acq_rel)\b'
450+
;;
451+
armv7)
452+
# Expect: dmb (data memory barrier) and ldrex/strex pairs.
453+
pattern='\b(dmb|ldrex|strex|ldrexb|strexb|ldrexh|strexh)\b'
454+
;;
455+
esac
456+
mnemonics=$(grep -oE "$pattern" /tmp/retro_atomic_test.s | sort -u)
457+
echo "$mnemonics"
458+
if [[ -z "$mnemonics" ]]; then
459+
echo
460+
echo '::error title=No barrier instructions emitted::retro_atomic_test.s contains no acquire/release/barrier mnemonics for ${{ matrix.arch }}; retro_atomic.h may have fallen through to a no-barrier backend.'
461+
exit 1
462+
fi

0 commit comments

Comments
 (0)