# Mali-G925 — SIGSEGV in `libGLES_mali.so` on every CPU-side Vulkan sync API after a compute + swapchain-present submit

Standalone, self-contained reproducer for the crash reported in
[ARM community thread 57769](https://community.arm.com/support-forums/f/mobile-graphics-and-gaming-forum/57769/).

## Environment observed

| | |
|---|---|
| Device | Samsung Galaxy Tab S11 (`SM-X736B`) |
| GPU | Mali-G925-Immortalis MC12 |
| Driver | `49.1.0` |
| Vulkan | 1.3.278 |
| OS | Android 15 |

## Symptom

After the app performs a `vkQueueSubmit` on a swapchain-present command buffer
(preceded by a compute submit on the same queue), **any** CPU-side
wait/idle Vulkan entry point called on the next frame SIGSEGVs *inside the Mali
ICD* (`libGLES_mali.so`). All four of these reproduce the same fault:

- `vkWaitForFences`
- `vkWaitSemaphores` (timeline)
- `vkGetSemaphoreCounterValue` (timeline)
- `vkQueueWaitIdle`

The crash lands within **0–6 frames**. Representative backtrace for the
`vkWaitForFences` variant:

```
#00 pc 0x0000000000021804  /system/lib64/libvulkan.so   (vulkan::api::WaitForFences+4)
#01 pc 0x00000000003b0c1c  /data/app/.../librepro.so     (frame+...)
```

The faulting address across all four variants clusters in `libGLES_mali.so`
near offset `0x99xxxx`, which suggests a single broken ICD helper that all four
caller-facing sync entry points route through.

> Production note: in the shipping app we worked around `vkWaitForFences` by
> migrating CPU↔GPU in-flight sync to timeline semaphores (`vkWaitSemaphores`),
> then found the timeline path crashes in the same ICD helper, and ultimately
> fell back to an EGL/GLES present path. This reproducer reproduces the crash on
> all four sync primitives so the ICD helper can be isolated.

## What the reproducer does

A single-translation-unit `NativeActivity` (no ncnn, no OpenCV, no third-party
Vulkan loader — only the platform `libvulkan.so` and `android_native_app_glue`).
Per frame, on the present queue, it runs the exact offending shape from the
production renderer:

1. **compute submit** — a trivial compute dispatch that writes a storage buffer
   (`vkQueueSubmit` #1, no present). This mirrors the RVM/dilate/clip compute
   chain that runs before present in the real app, and matches the "after
   compute" condition in the bug title.
2. `vkAcquireNextImageKHR` with a **binary** semaphore.
3. Record a command buffer: `UNDEFINED→TRANSFER_DST` barrier, a
   **staged host-visible buffer → swapchain image copy**
   (`vkCmdCopyBufferToImage`), `TRANSFER_DST→PRESENT_SRC` barrier.
4. **present submit** — `vkQueueSubmit` #2 that waits on the acquire semaphore,
   signals a binary render-finished semaphore **and** signals the per-slot
   in-flight sync object (timeline semaphore, or fence — see `SYNC_MODE`).
5. `vkQueuePresentKHR`.
6. **Next frame, top of loop:** the chosen CPU-side sync primitive waits on the
   slot's in-flight sync object → **SIGSEGV inside `libGLES_mali.so`**.

`kFramesInFlight = 2`, double/triple-buffered swapchain, `B8G8R8A8`/`R8G8B8A8`
auto-detected, FIFO present mode — same as the production swapchain.

## Selecting the sync primitive

`SYNC_MODE` is a compile-time switch in `app/src/main/cpp/repro.cpp`. Set it to
reproduce each backtrace independently:

| `SYNC_MODE` | In-flight sync object | CPU wait call on next frame |
|---|---|---|
| `SYNC_WAIT_SEMAPHORES` (default) | timeline `VkSemaphore` | `vkWaitSemaphores` |
| `SYNC_GET_COUNTER` | timeline `VkSemaphore` | `vkGetSemaphoreCounterValue` spin |
| `SYNC_WAIT_FENCES` | `VkFence` | `vkWaitForFences` |
| `SYNC_QUEUE_WAIT_IDLE` | (none) | `vkQueueWaitIdle` |

Either edit the `#define` or pass `-DSYNC_MODE=SYNC_WAIT_FENCES` to CMake via
`app/build.gradle`'s `externalNativeBuild.cmake.arguments`.

## Build & run

Requires Android Studio / Android SDK + NDK r26+ (or `ANDROID_NDK_HOME`).

```bash
cd repro/mali_g925_sync_sigsegv
./gradlew assembleDebug
adb -s <serial> install -r app/build/outputs/apk/debug/app-debug.apk
adb -s <serial> shell am start -n com.arm.repro.malig925/android.app.NativeActivity
adb -s <serial> logcat -s malig925-repro vulkan DEBUG
```

The app prints the picked physical device / driver / API version, then logs one
line per frame (`frame N: compute-submit … present`). The process dies with
`SIGSEGV` inside `libGLES_mali.so` within a few frames; `logcat -s DEBUG`
captures the tombstone backtrace.

A `gradlew` wrapper is intentionally not committed — run `gradle wrapper` once
(or open the folder in Android Studio) to generate it for your environment.

## Files

```
app/src/main/cpp/repro.cpp        # the whole reproducer (single TU)
app/src/main/cpp/repro.comp       # trivial compute shader (writes a buffer)
app/src/main/cpp/CMakeLists.txt   # compiles repro.comp → SPIR-V → embedded header
app/src/main/AndroidManifest.xml  # NativeActivity, no Java
app/build.gradle                  # ABI arm64-v8a only, NDK externalNativeBuild
```
