Skip to content

core: give each DeviceMemoryManager its own translation generations - #984

Merged
capy-ai[bot] merged 1 commit into
nx2from
fix/device-memory-translation-generation
Nov 9, 2026
Merged

capy-ai[bot] merged 1 commit into
nx2from
fix/device-memory-translation-generation

Conversation

@zortos293

@zortos293 zortos293 commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Every DeviceMemoryManager started its translation generation at 1 and bumped it on each Map and Unmap. The per-thread translation cache (06f294c) trusts an entry when the owner pointer and the generation both match. So a manager constructed at the address of a destroyed one, after the same number of Map and Unmap calls, matched translations a thread had kept for the old manager, and read through their host pointers into freed memory.

Each manager now starts 2^32 generations past the previous one's. Map and Unmap still bump it, so MapSerial() + TranslationGeneration() changes exactly as before. Nothing else changes.

How it was found: while porting the async read-back tests from #805 (EGLA-91). The [buffer_ports] fixtures build their manager in the same stack slot and map the same way, so with --rng-seed 1 13 of 44 cases read the previous case's guest memory. Each one passed when run alone, and all pass with this change, across seeds 1–12 and in declaration order. In the emulator, the same pattern needs a game relaunched in the same process while a thread that outlives the session reads device memory again. That's plausible, but nobody has seen it happen in a game. upstream/nx2 has the same code.

Checks: GCC 13 builds core/memory, video_core/gpu, memory_manager and gpu_device_memory_manager. The aarch64 -fsyntax-only check passes on core/memory, gpu and memory_manager. The tests that reproduce the failure are only on the async read-back branch, which isn't on nx2 yet, so this PR adds no test of its own.

Open in Capy

Summary by CodeRabbit

  • Bug Fixes
    • Device memory tracking now uses distinct translation generations across manager instances. Generations continue to advance as memory mappings change, improving consistency when multiple instances manage device memory.

The per-thread translation cache (06f294c) belongs to a manager when
both its owner pointer and the generation match. Every manager started
at generation 1 and bumped it once per Map or Unmap, so a manager built
where a destroyed one was, after as many Map and Unmap calls, matched
the translations a thread had kept for the old one and read through
their host pointers into freed memory.

The buffer_cache_ports read download cases hit this: each Fixture builds
its MaxwellDeviceMemoryManager in the same stack slot and maps the same
way, so in some random orders a case read the previous case's guest
memory (13 of 44 [buffer_ports] cases failed with --rng-seed 1, every
one passed alone). The emulator can hit it too when a game is relaunched
in the same process and a thread that outlives the session reads device
memory again.

Each manager now starts its generations 2^32 past the previous one's.
MemoryManager's MapSerial() + TranslationGeneration() still changes on
every Map and Unmap.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

DeviceMemoryManager now initializes each instance’s translation generation from an atomic counter that advances by 1ULL << 32. The documentation still states that Map and Unmap increment the generation.

Changes: Translation generation initialization

Layer / File(s) Summary
Per-instance generation base
src/core/device_memory_manager.h
Each manager now starts with a value from an atomic counter that advances by 1ULL << 32. The comments describe avoiding translation-cache matches after manager destruction and recreation.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Bug fix

Possibly related PRs

  • zortos293/Lichii#805: Changes the initialization of the same generation field using a process-wide atomic counter.
  • zortos293/Lichii#166: Adds the generation accessor used by a translation cache that invalidates entries when the generation changes.
  • zortos293/Lichii#858: Adds the generation accessor and uses the generation in serials that key memo validity checks.

Suggested reviewers: takoroka, camillelavey

Merge Risk: Minimal · up to 78a0c

The change separates initial translation generations between managers. No merge-blocking issue was identified.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change: each DeviceMemoryManager instance gets its own translation generations. The raw summary confirms the change initializes each instance's generation from an atomic count…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: each DeviceMemoryManager instance gets its own translation generations. The raw summary confirms the change initializes each instance's generation from an atomic counter advancing by 1ULL << 32, replacing the fixed initial value of 1. The title is specific, concise, and matches the changeset.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 1 files.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: aae791f2-b73d-44a2-9757-b43721113a14
Commits

Reviewing files that changed from the base of the PR and between cbb512e and 78a0c46.

Files selected for processing: 1

  • src/core/device_memory_manager.h

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review.

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@capy-ai
capy-ai Bot merged commit 0fff860 into nx2 Nov 9, 2026
5 checks passed
lucastaonline pushed a commit to lucastaonline/Lichii that referenced this pull request Nov 9, 2026
Merge origin/nx2 without replacing the complete zortos293#805 split with its partial
ports. Conflicting areas are resolved as follows:

- gpu_split.{h,cpp}, dma_pusher.{h,cpp}, gpu_thread.cpp, engines/maxwell_3d.cpp,
  engines/kepler_compute.{h,cpp}, engines/maxwell_dma.h and their fast-method
  tests retain zortos293#805's write classification, QMD/front view, position waits,
  tick/idle hooks, fast method path and sampled method timing. Do not import
  zortos293#949's reduced acquire table, back_quiet drain scheme or duplicate zortos293#1028
  write tracking. Keep the independent scheduler-owner check on CPU/service
  writes, the new RingFull drain site and opt-in live-read comparisons.
- engines/puller.{h,cpp}, nx2.h and query_cache.h combine zortos293#805's authoritative
  front semaphore view and source-tracking unbounded waiters with zortos293#979/zortos293#1014's
  unbounded region table and direct-release shadow. Mirror releases only when
  ViewApplies is false: unsplit, back macros, or delayed/high-accuracy releases.
  The full front view must not consume a second mirrored counter absent from
  its original releases. Note both destination writes on the fallback path.
  Compute max/reduction releases retain shadow combination and zortos293#805 checks.
  Keep zortos293#874's synced payload handling and FencedPayloadLanding: synced
  callbacks never advance fenced_landed.
- gpu.cpp, rasterizer_download_area.h, rasterizer_interface.h, memory_manager.cpp,
  buffer_cache.{h,base.h}, vk_buffer_cache.{h,cpp}, vk_rasterizer.{h,cpp} and
  buffer_cache_ports keep the full zortos293#805 read-back, upload ordering and tracing.
  Bring in zortos293#1028's review fixes: ClearDownload lands reads before DMA/discard
  writes, ClearReplacedDownload cancels only bytes reaching guest memory,
  and pending query results leave newer GPU writes dirty. Retain the new
  regression cases and the Qt-safe MayHoldGpuWrites boundary. Drop the unused
  reduced nx2_async_readback diagnostic implementation instead of duplicating
  zortos293#805's richer GPU-wait/read traces.
- device_memory_manager.h/inc use zortos293#984's per-manager generation ranges with
  Map/Unmap increments and preserve DownloadLanding/write tracking.
- nx2_profile.h and video_core.cpp combine zortos293#805's fine counters, sampling,
  scope depth and runtime switch setup with zortos293#965/zortos293#1039's zone-parent tracking,
  GPU waits, contention, worker/present/submit zones and stutter sites.
- vk_pipeline_cache.cpp takes zortos293#1036's v2 cache, migration, jobs, parallel
  translation and emission. shader_environment.{h,cpp} keeps zortos293#970's unsafe TIC
  reads; also omit v2's new pool-wide pretranslation flush, since workers no
  longer flush descriptors. Keep the AMD e0a992208d87eeb8 exception in
  pipeline_optimization.h.
- vk_graphics_pipeline.cpp, uniform_stream_reuse.h, sampled_views.h and tests
  converge zortos293#921/zortos293#949 with the adapted branch ports. PrepareImageView uses the
  single PreparesImage predicate, including valid views of NULL_IMAGE_ID.
  Keep uniform/descriptor delta replay, zortos293#974 fast-draw checks, zortos293#805 profiling
  and the empty-descriptor-range guard; no parallel duplicate paths.
- vulkan_device.cpp and vulkan_memory_allocator.cpp combine cache-v2 device
  support and zortos293#880's backable-image failure policy with zortos293#836's synchronized
  recycler and zortos293#831's 50 ms memory-usage cache. Preserve all remaining branch
  AMD, query, shader, texture and fast-draw changes in the automatic merges.
- nx2_video_decoder.cpp takes nx2's final dav1d/default-thread state. CMake test
  lists combine both sides; keep zortos293#805 fast-method tests rather than tests of
  rejected reduced split internals, and adapt waiter tests to >64 owners.
  Other matching cherry-picks converge to nx2; NX archives, HostTiming,
  memory/stutter/cache tooling and their sources are retained.

Validation before commit: git diff --cached --check passed; 30 cache_metrics
and cache_bench Python unit tests passed using workspace-local test data.
The requested clang-cl ThinLTO/PGO build is in progress. BUILD_TESTING is OFF;
C++ tests and in-game semaphore/read-back/cache/fast-draw validation remain
separate. No emulator launched or stopped; no push or other branch changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lucasaxm pushed a commit to lucasaxm/Lichii-anime-coop that referenced this pull request Nov 9, 2026
…ortos293#984)

The per-thread translation cache (06f294c) belongs to a manager when
both its owner pointer and the generation match. Every manager started
at generation 1 and bumped it once per Map or Unmap, so a manager built
where a destroyed one was, after as many Map and Unmap calls, matched
the translations a thread had kept for the old one and read through
their host pointers into freed memory.

The buffer_cache_ports read download cases hit this: each Fixture builds
its MaxwellDeviceMemoryManager in the same stack slot and maps the same
way, so in some random orders a case read the previous case's guest
memory (13 of 44 [buffer_ports] cases failed with --rng-seed 1, every
one passed alone). The emulator can hit it too when a game is relaunched
in the same process and a thread that outlives the session reads device
memory again.

Each manager now starts its generations 2^32 past the previous one's.
MemoryManager's MapSerial() + TranslationGeneration() still changes on
every Map and Unmap.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant