N64 On The Pandora


ok well just one more question, will there be a release at panda launch and will there be a bug report wiki or somewhere where the end users who actually have time to play through the games can report any bugs found that developers dont have the time to find?
 
Yes. There is already a basic compatibility template on the wiki. You can see what it looks like for the one PSX game I put in as an example. As soon as someone makes a page for Mupen64Plus, people can start submitting compatibility reports.
 
With all the compatibility question swirling, my own worry started to show itsself. However, as an owner of a 3rd gen Ipod Touch, I tried Zodttd's N64iphone, and was pleasantly surprised as to how many games were running. As this emulator is based on Ari64's code for Pandora, I have high hopes for the excellent emulation on N64 on Pandora.
 
Exophase said:
Can you quantify that statement with counts of branches vs memory access? I'm sure the latter will be more, but I'm interested in how much.
Typical integer programs on RISC processors have about 35% ld/st and 15% branches (dynamic count). I can provide stats about that for any simple (read: not multi-threaded) Linux program, but I'd also be interested to know the ratio for games on a machine with memory-mapped I/O, which might be very different.
 
Last edited by a moderator:
Laurent said:
Typical integer programs on RISC processors have about 35% ld/st and 15% branches (dynamic count). I can provide stats about that for any simple (read: not multi-threaded) Linux program, but I'd also be interested to know the ratio for games on a machine with memory-mapped I/O, which might be very different.

This time it's static count that's relevant, not dynamic. This will capture less executed and thus less optimized code that has more branches. In Ari64's case we also wouldn't necessarily be counting loads or stores that have offsets determined to be constant at compile time.

I doubt memory-mapped I/O means substantially higher incidence of loads/stores statically, unless the game is really hardcoded.
 
Last edited by a moderator:
Oh I see. But it's not exactly static count that you want, you want count of translated blocks, which might vary depending on what parts of the program are exercised.
Anyway in that case MM IO won't change the count a lot as you wrote (I was thinking about things such as looping for Vsync).
Sorry for missing the target :)
 
Exophase said:
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.

You have to check the value after you load it no matter what you do. Right now you're doing it using a test. An add wouldn't take more cycles. You could even use a cmn if you don't have a register to write to.
The add modifies the register, and will stall the subsequent load due to the additional cycle needed for address generation. The tst or cmn can execute simultaneously with the load.

Exophase said:
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.

Can you quantify that statement with counts of branches vs memory access? I'm sure the latter will be more, but I'm interested in how much. Personally, I don't think it's worth keeping lists of all branches either, which I assume is for self modifying code patching. But this is all quite secondary since the only function of this is to speed up games that heavily change their page tables, when you could just as well fall back on a different recompilation strategy.
Yes, for self-modifying code patching. I don't keep track of branches within a block, if the block gets hit with a write, it dumps the whole block. The only branches that get patched are those which jump between blocks. These are stored in the jump_out array, which contains linked lists for each 4K page. When a block gets hit with a write, it iterates the list and patches all the jumps so that they go back to the compiler.
 
Last edited by a moderator:
Ari64 said:
The add modifies the register, and will stall the subsequent load due to the additional cycle needed for address generation. The tst or cmn can execute simultaneously with the load.

Yeah, guess AGI strikes again. In which case I suppose if you can restrict the table to one half of the address space you can get by without modifying it and using the cmn.

Ari64 said:
Yes, for self-modifying code patching. I don't keep track of branches within a block, if the block gets hit with a write, it dumps the whole block. The only branches that get patched are those which jump between blocks. These are stored in the jump_out array, which contains linked lists for each 4K page. When a block gets hit with a write, it iterates the list and patches all the jumps so that they go back to the compiler.

So, do the old blocks still stay resident at the same locations and unused (until cache runs out) or do things eventually get relocated? Because if it's the former, you can patch the old block so that it looks up the new block, and if interblock branches are done with bl instead of b you should be able to patch the branch to it as well. This should be less overhead than maintaining a branch list and patching all branches as the modification happens (many branches will be from blocks that themselves will be on the chopping block for modification). It's not that big of a deal though, since this is mostly constant time overhead.
 
Last edited by a moderator:
Exophase said:
Ari64 said:
The add modifies the register, and will stall the subsequent load due to the additional cycle needed for address generation. The tst or cmn can execute simultaneously with the load.

Yeah, guess AGI strikes again. In which case I suppose if you can restrict the table to one half of the address space you can get by without modifying it and using the cmn.
I don't have to restrict it to half the address space if I can use the 'free' shift by two.

Exophase said:
Ari64 said:
Yes, for self-modifying code patching. I don't keep track of branches within a block, if the block gets hit with a write, it dumps the whole block. The only branches that get patched are those which jump between blocks. These are stored in the jump_out array, which contains linked lists for each 4K page. When a block gets hit with a write, it iterates the list and patches all the jumps so that they go back to the compiler.

So, do the old blocks still stay resident at the same locations and unused (until cache runs out) or do things eventually get relocated? Because if it's the former, you can patch the old block so that it looks up the new block, and if interblock branches are done with bl instead of b you should be able to patch the branch to it as well. This should be less overhead than maintaining a branch list and patching all branches as the modification happens (many branches will be from blocks that themselves will be on the chopping block for modification). It's not that big of a deal though, since this is mostly constant time overhead.
Yes, the old blocks stay, and sometimes they are recovered if found to be unmodified. The reason for this is primarily the MIPS interrupt handler in the first page of memory. That page is always getting hit with writes, even though the interrupt handler rarely changes. So I restore the existing block rather than recompiling it thousands of times.
 
Last edited by a moderator:
Back
Top