SNES: CX4 cache and timing improvements#7
Conversation
- Remove extra cycle on bus access, is now 1+WS as expected - When preloading cache by writing to $7F48, skip runtime checks and load the page immediately - Program bank is set when writing to $7F48, as expected by MMX2/3's CX4 setup subroutine
|
Thanks for this PR! I'm not very familiar with SNES, but this code looks solid and it fixes the Mega Man X2 demo's boss fight, which desyncs without this change. I'm inclined to merge it in. Can you give us permission to relicense this code in the future? Sour was working toward removing GPL content from Mesen with the goal of relicensing to something like MIT, so we'd like to ensure nontrivial submissions permit that. |
|
I found that this PR is triggering a nullptr exception in cx4test: https://snescentral.com/article.php?id=1116 I don't understand exactly why yet. It's happening the first time Cx4::ProcessCache is called and has the following stack trace: ProcessCache is called with address 0. In this function, ReadCx4 gets a handler and avoids using that handler because it's nullptr, returning 0. However, the following GetAccessDelay gets the same nullptr handler and uses it regardless of its value, causing the exception. In the original code, these functions weren't reached at all on the first call to ProcessCache because it instead reaches the first return, which is now skipped because _state.Cache.Preload == true. I don't know which path through the first call to this function is actually correct. If I add the same if(handler) check to GetAccessDelay, then cx4test runs and all of its tests pass. It makes sense that if one of these functions has this check, the other should, as well, but I don't understand why we're in this situation and if it means there's something else wrong. |
|
It looks like the cache is enabled by case 0x7F48 in Cx4::Write, and then on the next call to Run, ProcessCache gets run. It ends up working on address 0 because that's what it gets from (_state.Cache.Base + (_state.PB << 9)) & 0xFFFFFF. (I don't know if those variables have even been set by anything yet. I presume they haven't, since they're both 0.) There's apparently no mapping for address 0, so there's no handler. I'm inclined to think that the new behavior is a step in the right direction because the old code was returning at a location commented with "Current cache page matches the needed address, keep using it", which doesn't seem plausible right when the cache is enabled. The question, then, is what 'Populate the cache' should do when the address being read has no mapping. |
State classes exist in both the Core and UI and they must be kept in sync when changes are made to them. Failing to do this apparently causes unpredictable bad behavior. Thanks to Sour for making me aware of this issue in the first place.
If Cx4 reads an address that doesn't have a mapping, the actual read verifies that the handler exists before using it, but GetAccessDelay does not, causing a nullptr dereference. This change returns a default 1 if the handler doesn't exist. This bug was triggered by cx4test when using AkiteruSDA's CX4 timing improvements. It was likely possible before that, but wasn't triggered by any known software. With this fix, cx4test passes and no longer crashes.
|
I've written a couple fixes that are committed on my branch of this repo, here: https://github.com/nesdev-org/MesenCE/commits/fiskbit-cx4-fixes/ One of them fixes the nullptr handler issue. The other fixes a desync of the Cx4State struct between the Core and UI; these need to be kept in sync or it can cause all sorts of problems with the debugging tools. I tried to push these commits to your PR, but I don't seem to have permission. Can you pull them into this PR? We'll also still need permission from you to be able to relicense your commit in the future (such as to MIT). With these changes, I don't know that the CX4 implementation is actually acting correctly in the situation it was previously crashing in, but it at least doesn't crash and passes all the available tests. |
|
Thank you for these fixes! I merged those into my branch. I think there's a possibility that I'm fine with relicensing as well, no worries there. I'm not sure why the Linux builds are failing. I could try merging the CE Edit: I just merged |
These are the same changes as were recently merged into bsnes, SNES_MiSTer, and Mesen2RTA.