kin
Member
what about waverace64?
LOL... people keep asking why certain games don't work, but they don't like the answersExophase said:I hope we can turn every thread about Mupen64plus into a coding discussion ;D
fp contains a pointer to some local variables at different offsets, and there would be enough space to put the mapping table at the end. This saves an instruction compared to loading the address with movw/movt.Exophase said:- Okay, I assume the emulated page table starts after #offset, but how important are the things in that region, such that you'd have fp point there instead of + offset forward? If you generally access things via immediate offsets from that point then it'd just be with negative offsets, but I think it's unlikely that you're doing anything that hits more frequently than memory code. But, this could be a compatibility issue against your normal mode of operation. If the offset isn't required you can load from (fp + ((address >> 12) << 2)), not that I need to tell you that.
add r2, fp, #offset
lsr r3, r1, #12
ldr r2, [r2, r3 lsl #2]
It can be difficult to generate the address in advance, often reads/writes occur one after another, such asExophase said:So if possible it'd be good to schedule the address generation in advance where you can. ... you probably already knew this too, but just in case.
LW r3, 4(r2)
LW r4, 8(r2)
LW r5, 128(r6)
LUI r1, 89ab
LW r2, 1234(r1)
That may work for GBA emulation without a MMU, but with 4K pages the miss rate is going to be way too high. (Consider a loop incrementing a pointer that crosses a page boundary.)Exophase said:- There's an alternative for MMU or complex memory space emulation that hlide suggested to me once, and I use in the MIPS version of gpSP.. I'm not sure yet if it would work out better for you or not, but the idea is to check if the entry is the same page as it was last time, and if so, use a direct routine. I think this would probably work better on x86 or a platform with larger immediates than on ARM, because you need to do a pretty large compare and immediate add. For ARM I think it'd be something like this:
Code:ubfx r2, r1, #12, #20 movt r0, #offset_low movt r3, #last_memory_low movw r0, #offset_high movw r3, #last_memory_high cmp r2, r3 blne memory_miss ldreq r0, [r0, r1]
ubfx r2, r1, #12, #20 is equivalent to lsr r2, r1, #12... I doubt the ubfx encoding is any faster.Exophase said:Of course, this only works well if the memory accesses hit the same pages usually. Otherwise it incurs the costs of self-modifying code to update the offsets, which probably wouldn't be quite as bad if it didn't require going through the kernel. This uses more code space and worse, more registers, but I think it'll usually take less cycles since it pairs much better/has fewer stalls. This is assuming that the ubfx takes one cycle, since the TRM doesn't mention it. It probably has the usual shift interlock.
What glitch?ashdjones said:While we're all here asking, is the Fzero-X glitch serious? Its my favourite racing game ever!
Looks fine to me.kin said:what about waverace64?
Ask Adventus.HackModford said:Hey Ari64! Do you think the enhanced texture packs will ever be possible?
Do you mean that your code generator will always use strict basic blocks? Couldn't you accept that a sequence of instructions can be translated multiple times depending from where you enter it or would that make other things such as SMC more difficult?Ari64 said:It can be difficult to generate the address in advance, often reads/writes occur one after another, such asExophase said:So if possible it'd be good to schedule the address generation in advance where you can. ... you probably already knew this too, but just in case.
Even if you have enough registers to do it, you also need to make sure there are no intervening branch targets.Code:LW r3, 4(r2) LW r4, 8(r2) LW r5, 128(r6)
It will in some cases translate things differently depending on where you enter. I try to keep this to a minimum because it makes very inefficient use of the L1 cache to have this stuff translated multiple times.Laurent said:Do you mean that your code generator will always use strict basic blocks? Couldn't you accept that a sequence of instructions can be translated multiple times depending from where you enter it or would that make other things such as SMC more difficult?
Not every instruction, just in certain cases such as after an unconditional jump instruction.Laurent said:So when you translate a block you check whether each PC in that block has already been translated? If it's what you're doing I wonder how better/worse it is compared to having different translations. The typical trade-off that can't be decided unless one spends a good amount of effort benchmarking different programs.
// Don't recompile stuff that's already compiled
if(check_addr((u_int)addr+i*4+4)) done=1;
Try dexanoid or the roms from pdroms.de. Not all of them work though. Define assem_debug if you want to see the output.Laurent said:I'd need to spend some time looking at your code... Is there some easy way to check mupen output without having to run a real ROM (which I don't have since I don't own any N64 game)?
Ari64 said:fp contains a pointer to some local variables at different offsets, and there would be enough space to put the mapping table at the end. This saves an instruction compared to loading the address with movw/movt.
Ari64 said:but either way you end up with stalls due to shifts and loads.
Ari64 said:That may work for GBA emulation without a MMU, but with 4K pages the miss rate is going to be way too high. (Consider a loop incrementing a pointer that crosses a page boundary.)
Ari64 said:ubfx r2, r1, #12, #20 is equivalent to lsr r2, r1, #12... I doubt the ubfx encoding is any faster.
MDave said:I have always wondered if making your own n64 rom that runs specific code would help with optimising / figuring out problems with the emulator that normal debugging wouldn't help with. Instead of trying to understand what the n64 game is trying to do when it crashes/glitches etc?
Exophase said:You can't really make a ROM from scratch to fix a glitch in an N64 game because the entire problem is you don't know what the glitch is. If/once you understand that then the game is usually a perfectly suitable test case for a new implementation, and even if you could do a simpler synthetic test (although it'd be a lot of work just writing it) you wouldn't know if it really captures everything exactly like the real game does. And getting the real game working is much more important.
Awakening said:But wouldn't it help in benchmarking different solutions when trying to figure out the most optimized way of doing things, to make the emulator run faster? Unless it takes longer to do this then using existing ROMs.
Ari64 said:Ask Adventus.HackModford said:Hey Ari64! Do you think the enhanced texture packs will ever be possible?
There are separate mapping tables for reads and writes (pages can be write protected) so these need separate offsets. Maybe some games will be okay with a single table, I don't know.Exophase said:The point I was trying to make was to have fp point to the mapping table and use negative indexing to reach the local variables. This is assuming that you have < 4KB of them.
I am sure this optimization will work for some games. I doubt it will work in all cases though. I believe there are games which do demand paging, loading pages into RAM when a read from an unmapped page occurs.Exophase said:Yes, if you're emulating the TLB behavior precisely. However, if the game has a fixed address mapping (as I referred to) and is just using the TLB to cache a page table then you can effectively ignore TLB evictions and keep mapping up the same table. Having a single large virtual address space is certainly the model I would expect games to follow. At any rate, you can still determine if the address space is modified, and if so flush the translation - and if it happens frequently, choose a different mapping strategy. So long as games don't rely on protecting and catching accesses to areas that were previously allowed you won't have a problem, and I don't suspect this is normal behavior for N64 games.
I don't know of any, but I haven't tested every N64 game.Exophase said:The only problem is if games have a process switching OS, but I can't really imagine why they'd want to incur this sort of needless expense.
Ari64 said:There are separate mapping tables for reads and writes (pages can be write protected) so these need separate offsets. Maybe some games will be okay with a single table, I don't know.
Otherwise this would work, the local variables are about 1KB.
Ari64 said:I am sure this optimization will work for some games. I doubt it will work in all cases though. I believe there are games which do demand paging, loading pages into RAM when a read from an unmapped page occurs.
Ari64 said:I don't know of any, but I haven't tested every N64 game.
Ari64 said:What glitch?ashdjones said:While we're all here asking, is the Fzero-X glitch serious? Its my favourite racing game ever!
Well, the other drawback is that the shift/add will take an additional cpu cycle. I don't know what's worse, an additional cycle for every memory access or cache and register pressure from having two tables.Exophase said:Ari64 said:There are separate mapping tables for reads and writes (pages can be write protected) so these need separate offsets. Maybe some games will be okay with a single table, I don't know.
Otherwise this would work, the local variables are about 1KB.
My current approach to unify two tables is this: the most significant bit set determines if something is writable or not, and all zeros determines that it can't be read from. When the remap entry is loaded it's shifted to the left by 1 (or added to itself). The carry flag is checked for stores, and the zero flag is checked for loads.
The one drawback to this approach is that it only allows read-write, read-only, or invalid - it doesn't allow write-only. But this is only an issue if the MMU in question supports write-only. If it does, you access a second table to catch false-positives on stores.
That's how I do branches, keep a list and then patch them. There are going to be a lot more loads/stores than branches, so it might not be worth it.Exophase said:Ari64 said:I am sure this optimization will work for some games. I doubt it will work in all cases though. I believe there are games which do demand paging, loading pages into RAM when a read from an unmapped page occurs.
You can still support this in the following fashion: by default, load and store code is emitted such that they check the page table entry, and if it's valid the routine is patched to the one I originally gave. This is only a problem if the start routine requires more bytes than the "usual" one I gave; chances are that instead it'll take less and will have to be padded out with nops. It does mean every load/store would have to be modified at least, but it was going to be this way anyway (unless you could reliably predict which page an access will use). This shouldn't be a big deal unless you end up cycling the code a lot, in which case you probably have bigger problems.
It's if they go back to being unmapped that you have a bigger problem, as you'll have to flush the translation cache. That is, unless you keep a list of all memory references keyed by which page they're assigned to.
Ari64 said:Well, the other drawback is that the shift/add will take an additional cpu cycle. I don't know what's worse, an additional cycle for every memory access or cache and register pressure from having two tables.
Ari64 said:That's how I do branches, keep a list and then patch them. There are going to be a lot more loads/stores than branches, so it might not be worth
it.