test(agc): reproduce an import window over a pending storage image
agc_driver_dcc_keys_first_write only exercises the import window when the device's own host import resolves the write protection of the imported pages, as the NVIDIA driver does on Linux. CI's devices do not, so the comparison that #2984 adds for BlockMaybeWritten units is not run there. RunImportWindow opens an import window itself over a pending storage image with GuestMemory::ImportWatched and stores the byte already there, which leaves the same stamp without depending on the driver: the image must stay pending and unchanged. A store of a new byte inside a window must still be seen. Devices whose imports are compared print that the window is not tested, as the recorder's (w) test does. Linux only: the Windows path compares imports. TechnicalDebt records that an identical-byte CPU store inside a window cannot be told apart from the import, and that images tracked by layer still upload again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f774e1cd98
commit
ee720ee004
|
|
@ -397,6 +397,48 @@ void RunUntrackedReuse(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
|||
Require(image->Refresh(), "the GPU-edited image was uploaded twice");
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// An import window stamps the pages whose write protection it saw resolved as a possible CPU
|
||||
// store. A driver that pins imported pages for writing (NVIDIA on Linux) resolves it without
|
||||
// storing anything, which a store of the byte already there reproduces on any device.
|
||||
void RunImportWindow(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
auto* texels = block;
|
||||
auto* keys = block + KeysOffset;
|
||||
const auto surface = AddressOf(texels);
|
||||
std::memset(texels, 0x55, SurfaceBytes);
|
||||
std::memset(keys, 0x00, KeyBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
const auto texture = TextureDescriptor(texels, keys);
|
||||
std::vector<std::uint32_t> writeData(16, 0u);
|
||||
std::copy(texture.begin(), texture.end(), writeData.begin() + 4);
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Dispatch(device, WriteCode, writeData);
|
||||
const auto image = StorageTexture::FindPending(surface, SurfaceBytes);
|
||||
Require(image != nullptr, "import window: the written surface has no pending image");
|
||||
if (!AgcDriver::GuestMemory::Watched(surface, SurfaceBytes)) {
|
||||
std::puts("host imports are compared, not watched: the import window is not tested");
|
||||
StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
return;
|
||||
}
|
||||
const auto storeInWindow = [&](std::uint8_t value) {
|
||||
auto* page = reinterpret_cast<volatile std::uint8_t*>(texels);
|
||||
Require(AgcDriver::GuestMemory::ImportWatched(surface, SurfaceBytes, [&] {
|
||||
page[0] = value;
|
||||
return true;
|
||||
}), "import window: the import failed");
|
||||
};
|
||||
const auto pendingVersion = image->Version();
|
||||
storeInWindow(0x55);
|
||||
Require(image->Refresh(), "import window: a pending image was uploaded again after an import that stored nothing new");
|
||||
Require(image->Version() == pendingVersion && StorageTexture::FindPending(surface, SurfaceBytes) == image, "import window: an import that stored nothing new wrote the pending image back");
|
||||
storeInWindow(0x66);
|
||||
Require(!image->Refresh(), "import window: a CPU store within an import window was missed");
|
||||
StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
void RunSharedImport(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
using namespace AgcDriver::GuestMemory;
|
||||
|
|
@ -436,6 +478,7 @@ int main() {
|
|||
std::optional<GuestBlock> watched;
|
||||
std::optional<GuestBlock> untracked;
|
||||
std::optional<GuestBlock> shared;
|
||||
std::optional<GuestBlock> window;
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
block.emplace(false);
|
||||
|
|
@ -450,6 +493,10 @@ int main() {
|
|||
if (AgcDriver::GuestMemory::WriteWatched()) {
|
||||
watched.emplace(true);
|
||||
Run(*device, watched->Data());
|
||||
#ifndef _WIN32
|
||||
window.emplace(true);
|
||||
RunImportWindow(*device, window->Data());
|
||||
#endif
|
||||
} else {
|
||||
std::puts("guest memory has no write watch: a write-watched surface is not tested");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- the handler is installed when libc loads and passes other faults to the handler installed before it. A handler installed later has to do the same.
|
||||
- [Pending storage-image results](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). `APS5_STORE_AT_FLIP=1` stores every pending result at each flip; storing them at the title's GPU waits (labels, fences) is unimplemented.
|
||||
- Windows host-pointer imports of the guest mapping itself are compared instead of write-watched: a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. [GuestMemory](../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. This preserves CPU edits through byte comparisons but disables snapshot reuse and unit shadows for excluded blocks; restoring tracking on import retirement without replacing the backing and measuring the comparison cost remain unimplemented.
|
||||
- [Storage-image units under a Linux import window](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) that the window stamped as a possible CPU store (`BlockMaybeWritten`) are compared with the bytes the image was uploaded from or written back as, and are unchanged when those still match. A CPU store of the bytes already there within the window cannot be told apart from an import that resolves the write protection without storing, as the NVIDIA driver (610.57.04) does for every imported page, so such a store does not win over pending results. Images tracked by layer instead of by 64 KiB unit still read the window as a change and upload again.
|
||||
- [shm_open](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) creates only anonymous objects (`SHM_ANON`), each backed by a host anonymous file: a `memfd` on Linux, a delete-on-close temporary file on Windows. The flag checks follow FreeBSD 11's `kern_shm_open`; the mode is ignored. Named objects throw. The descriptor cannot be mapped yet: `mmap` accepts only private anonymous mappings and fails a descriptor mapping with `EOPNOTSUPP`, so the known caller (the bundled Mesa in PPSA59322, which truncates the object and maps it `MAP_SHARED`) gets that failure
|
||||
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
|
|
|
|||
Loading…
Reference in New Issue