notaz said:
Thinking about making it static to keep it all simple. Not going for some crazy 8-pass solution like Ari64 has. The most likely target is Pandora anyway, which shouldn't need something super efficient.
I think this will work fine, especially if you can keep 10+ registers available for allocation. Merging local data with the stack is a good way to get another register. The dynamic hybrid I mentioned would just involve using register caching (not allocation) in the remaining registers used for temporaries, just to avoid reloading/storing non-allocated registers used consecutively over short periods.
Naturally you should profile games to see which registers they use the most, possibly with game specific setups for the most demanding ones if it varies much.
notaz said:
Not sure what do you mean here? 128kB array of flags for every halfword?
Essentially yes. What I do in gpSP is have an array offset from the RAM writable regions that's the size of the region, and stores first check to see if there's something at address + offset. If there is they go to an SMC handling routine. The actual array contains block offset indexes, so it has two purposes. A 0 in either byte means that there's no block there, otherwise it identifies which block you're overwriting. There's also a bit that determines if the block starts there or not. This means there are less than 32K entries, but you shouldn't end up with so many blocks in RAM anyway.
Unless you can practically mark entire memory pointer LUT regions then I think this is a good option, because you have to do the additional check per-write anyway. This check is relatively fast and has no false positives. It is however harder on cache.
notaz said:
Today I've added an array of flags which tell if any particular 4kB chunk of RAM has code compiled from it, and check for them in write handler. That allows RAM changing games to work but I get lots of false hits, obviously 4kB chunks are too large (right now it runs through all block descriptors to verify if it really hit a block).
Yes, you definitely want an array per RAM area for blocks rather than that. You could of course use a bitmap as well, so it's really not that much space. But keeping it the same size as the RAM areas lets you use a constant offset and store block indexes.
If you do decide to stay with a first check using larger blocks then you can do what Ari64 does, which is to remove the modified blocks from whatever it is that's getting caught per write and patch them with self-checking code that determines upon block start if the block has been modified. It's pretty easy to see how this can horribly backfire, though.
notaz said:
That on-chip RAM functionality is annoying, Virtua Racing is rewriting it every 1-2 frames and running code from there, which means I will have to keep recompiling it. Actually this is what SVP version does too, but since it was a recompiler for a single game I was able to enumerate all IRAM states and avoid constant recompiling. This ain't going to work here as VF also seems to do the same (haven't properly verified though, pretty tired already).
Is it rewriting the entire thing or do some functions stay resident? Either way, 2KB doesn't span that much code so I think you should be OK even recompiling the entire thing every frame. It's not like some GBA games I know that modify bytes in code hundreds or even thousands of times per frame.
Do games use the overwrite feature of the VDP a lot? If they do then the function pointer LUT approach might remain a better idea. There's actually something you can do which is sort of inbetween (credit to hlide for this approach, gpSP targeting MIPS uses it). For each memory access check the region and if it's wrong call a function to patch it to check the new region and use a new handler. Then have an instruction to either perform the access or call a function to do it. This shouldn't be much worse than function pointer LUTs in the function calling cases (subtract, compare, conditional call, function call vs shift, load, indirect function call) and would be better for the direct cases. It doesn't handle mirroring as well, but you indicated that's not a problem.