Mupen64Plus


Exophase said:
I think most of those operations aren't that frequent, if float ops are really only 2-4% of the code. Are you still using soft floats for everything, or are you only using them for double precision/unsupported operations? StrmnNrmn claimed that games he tried were okay with having their doubles turned to floats.
I'm using whatever gcc generates, I haven't really tried optimizing floating point.

Inaccurate floating point code can introduce subtle bugs. For example, the original mupen64plus was rather sloppy about setting the floating point rounding mode, and there was a lot of complaining on the emutalk forums about the problems this caused in Banjo-Tooie.
 
Last edited by a moderator:
Exophase said:
Then, do you know how many per-frame can't be detected via constant propagation? For any given game.
It varies, but typically around half of memory-mapped I/O operations can be detected via the constant propagation, out of a total of around 10000-20000 per second.
 
Last edited by a moderator:
Hey... Just thught of something. Mupen64Plus can run the rice video plugin. Or at least the linux version can. First, can the Pandora version run that plugin (eventually), and second, if it can, will we be able to work High Rest textures for games?

(I may seem too expectant. But I'm not expecting anything. Just asking about eventual support.
 
Ari64 said:
Exophase said:
Then, do you know how many per-frame can't be detected via constant propagation? For any given game.
It varies, but typically around half of memory-mapped I/O operations can be detected via the constant propagation, out of a total of around 10000-20000 per second.

That's not bad! But probably too many to want to go through a SIGSEGV handler, unless the overhead really is only in the dozens of cycles. You'd still probably need at least 20-30x RAM operations for it to really be beneficial.
 
Last edited by a moderator:
Exophase said:
Ari64 said:
I guess I can try it. It looks like movt/movw is encoded as cmp/tst with the s bit clear. ARM's documentation on this stuff totally sucks. :(

A lot of instructions ARMv5+ are encoded in that particular space (what looks like the test instructions with S clear)
The only other one that I use is clz, which is cmn r15, rm, lsl r15.

uxth is encoded as a load with a register-specifed shift. pld is a load with the condition flags set to all ones.

The ARM instruction set gets more and more bizarre as they try to cram stuff into otherwise invalid encodings.

And no thanks to ARM for hiding documentation behind a registration page where I can't search for it. :(
 
Last edited by a moderator:
Exophase said:
Ari64 said:
Exophase said:
Then, do you know how many per-frame can't be detected via constant propagation? For any given game.
It varies, but typically around half of memory-mapped I/O operations can be detected via the constant propagation, out of a total of around 10000-20000 per second.

That's not bad! But probably too many to want to go through a SIGSEGV handler, unless the overhead really is only in the dozens of cycles. You'd still probably need at least 20-30x RAM operations for it to really be beneficial.
You really don't want 10000 pagefaults per second, neither the kernel nor the CPU is optimized for that.

Besides, a lot of N64 software is poorly written and dereferences bad pointers. I really don't want to have to track down any more bugs from games that scribble all over memory, there are enough of those as it is.
 
Last edited by a moderator:
Ari64 said:
You really don't want 10000 pagefaults per second, neither the kernel nor the CPU is optimized for that.

Besides, a lot of N64 software is poorly written and dereferences bad pointers. I really don't want to have to track down any more bugs from games that scribble all over memory, there are enough of those as it is.

I think you give the CPUs too little credit, ARMs historically have good IRQ latency. There are even GBA games that push more interrupts than that. Of course Cortex-A8 is going to be worse since it has a long pipeline, but it should still be within a few dozen cycles roundtrip. I can't speak for Linux, all I know is that someone who wanted to virtualize GBA said that the handler was within dozens of cycles itself, on x86 at least. You could possibly make your own hook for it that's almost as fast as it'd be w/o Linux if you feel that it's too much. Anything within 100 cycles is only 1-2million cycles per second, which sounds like a lot but really isn't.

I don't see any reason why this would make strange accesses more difficult to track down. It's just a different way of calling a handler.
 
Last edited by a moderator:
Exophase said:
I think you give the CPUs too little credit, ARMs historically have good IRQ latency. Of course Cortex-A8 is going to be worse since it has a long pipeline, but it should still be within a few dozen cycles roundtrip. I can't speak for Linux, all I know is that someone who wanted to virtualize GBA said that the handler was within dozens of cycles itself, on x86 at least. You could possibly make your own hook for it that's almost as fast as it'd be w/o Linux if you feel that it's too much.

I don't see any reason why this would make strange accesses more difficult to track down. It's just a different way of calling a handler.
The test takes 1 cycle, and generally the branch will be correctly predicted by hardware. At most you have a branch misprediction penalty if it isn't. The overhead of a page fault is much greater.

Besides, what you propose would expose the process's entire memory map to the emulated environment, creating a vector for malware distribution in hacked N64 roms. That is just a really really bad idea.
 
Last edited by a moderator:
Ari64 said:
The test takes 1 cycle, and generally the branch will be correctly predicted by hardware. At most you have a branch misprediction penalty if it isn't. The overhead of a page fault is much greater.

That's +2 instructions, plus a third one for the address generation that often isn't necessary for every load/store. That's a lot of extra icache pressure too. So, there are 10-20 thousand of these per second, how many to-RAM load/stores are there? Memory accessing instructions usually take 20-30% or more on RISC platforms so if a game executes 10m instructions (of the 18m you're giving it) then that's 2-3m loads and stores. You're acting like this is absurd but I'm pretty sure you can do the math on this one.

Ari64 said:
Besides, what you propose would expose the process's entire memory map to the emulated environment, creating a vector for malware distribution in hacked N64 roms. That is just a really really bad idea.

So I guess whitelist the feature? Given that there are only a handful of N64 ROMs most people even really want to play.. Besides that, if people wanted someone to run arbitrary user space code on Pandora I'm sure there are easier ways to do it than by exploiting an emulator. Like distributing an emulator, for instance.
 
Last edited by a moderator:
Exophase said:
So I guess whitelist the feature? Given that there are only a handful of N64 ROMs most people even really want to play.. Besides that, if people wanted someone to run arbitrary user space code on Pandora I'm sure there are easier ways to do it than by exploiting an emulator. Like distributing an emulator, for instance.
This code is going to run on more platforms than just the Pandora and I'm not going to create a huge exploit like this just because it saves 0.5% of the CPU time (assuming 3 million cycles at 600MHz).
 
Last edited by a moderator:
Ari64 said:
This code is going to run on more platforms than just the Pandora and I'm not going to create a huge exploit like this just because it saves 0.5% of the CPU time (assuming 3 million cycles at 600MHz).

If that's how you feel about it, then I wonder where all the rest of your CPU cycles are going at a max of 18m emulated per cycle? Maybe you'll find a good breakdown of it. You'd possibly save a lot more in lower icache footprint, not to mention BTB pollution with branch heavy code. They could mispredict even when they're doing the same things they always do. I never expected you to actually do it (I doubt I'd do it either, when there's such a nice memory mapping already present), just don't act like this way is automatically slower. Not that I think you'd even try, but I'm curious to see if it'd really only be 0.5% difference in CPU time.

Still, frankly I'd be shocked if anyone actually tried exploiting such a thing, for any platform. And you disregarded my whitelist comment. Enh, oh well.
 
Last edited by a moderator:
Exophase said:
If that's how you feel about it, then I wonder where all the rest of your CPU cycles are going at a max of 18m emulated per cycle? Maybe you'll find a good breakdown of it. You'd possibly save a lot more in lower icache footprint, not to mention BTB pollution with branch heavy code. They could mispredict even when they're doing the same things they always do. I never expected you to actually do it (I doubt I'd do it either, when there's such a nice memory mapping already present), just don't act like this way is automatically slower. Not that I think you'd even try, but I'm curious to see if it'd really only be 0.5% difference in CPU time.
It does seem surprising that 18 million original instructions take up ~300 million cycles. Cache misses and memory latency is part of it, this time tends to remain fixed when you increase the CPU core clock, so you can get a pretty good estimate of this by changing the clock speed. The rest is going to be very hard to get a good breakdown of, especially things like branch misprediction.

But, the cycle counting probably imposes a 10% overhead, and at least another 10% for the register cache/writeout. Floats are really expensive, minimum 10-20 cycles with the A8's FPU, so even if only 3%, those 540000 instructions take up 5-10 million cycles (and probably more). The interrupt handler was consuming about 5% last I checked.

The dirty page check for writes is really expensive, at least 5 cycles per write. Maybe a SIGSEGV handler could improve this, but I don't know how the MMU would handle a heavily fragmented memory map, it could significantly degrade TLB performance.

Exophase said:
Still, frankly I'd be shocked if anyone actually tried exploiting such a thing, for any platform. And you disregarded my whitelist comment. Enh, oh well.
When you leave open obvious holes like this, sooner or later someone will exploit it. Especially when it's as easy as offering noobs free romz.
 
Last edited by a moderator:
Laurent said:
Do you have any estimate of how much time is spent generating code vs running it?
It uses a few seconds at startup, and beyond that very little.

It should use zero after the first recompilation, but a persistent problem has been code that unexpectedly jumps into the middle of existing blocks. Consider the following (simplified) example:

Code:
80001200: LUI r9,10000
80001204: ADDIU r8,r8,1
...
80001240: BNE r8,r9,80001204
80001244: SLL r0,r0,0

Everything is 32-bit, so the compiler generates 32-bit code. What happens when something jumps into the middle of this and potentially has a 64-bit value in r8 or r9? We have to recompile it as 64-bit. There's some code at the start of the register allocation pass to try to prevent such cases by forcing the comparisons to be 64-bit if there's a risk of this, but it still ends up recompiling stuff that it shouldn't need to.
 
Last edited by a moderator:
Laurent said:
Do you have any estimate of how much time is spent generating code vs running it?

The longer you run the game the smaller that percentage will be (unless the emulator is running out of translation cache repetitively or modifying its code). I never really get why people act like code recompilation should be a fixed percentage of execution time. I also don't get why people don't take this into consideration for benchmarks on recompilers meant to run games, instead just running them for a minute. It's not really a fair representation. Recompilation might cause occasional stalls, but I guarantee you that it won't annoy most users nearly as much as a fixed performance penalty that's constantly there.

Ari64; Have you looked at the performance monitoring capabilities of the Cortex-A8? You would have to write a kernel module for it (which would have its own performance hit when being reading back the values, unfortunately) but other than that it would let you directly monitor up to 4 event counters at once. You can monitor dcache/icache misses, branch mispredictions, and so on. Really such a module would be good for a lot of people so it'd be great if someone did one, if it hasn't already been. Or it could be made part of the kernel proper.
 
Last edited by a moderator:
That's very interesting. This basically means you don't have self-modifying code and no gencode cache capacity, right?
In that case, I wonder if some static compilation could be applied. That'd require some runtime profiling of generated code to identify hotspots that could benefit from heavier optimization.

But it's probably useless: I guess in the end HLE tricks will bring more speedups :)
 
Exophase said:
The longer you run the game the smaller that percentage will be (unless the emulator is running out of translation cache repetitively or modifying its code). I never really get why people act like code recompilation should be a fixed percentage of execution time. I also don't get why people don't take this into consideration for benchmarks on recompilers meant to run games, instead just running them for a minute. It's not really a fair representation. Recompilation might cause occasional stalls, but I guarantee you that it won't annoy most users nearly as much as a fixed performance penalty that's constantly there.
I will speak for myself in that case :) Don't many old games/demos from the i386 era use SMC? Since I'm interested in that, it is legitimate to ask for the cost of translation, I guess.
I agree that for ROM-based machines, one doesn't have to really consider that cost (unless it's so heavy it's noticeable).

Ari64; Have you looked at the performance monitoring capabilities of the Cortex-A8? You would have to write a kernel module for it (which would have its own performance hit when being reading back the values, unfortunately) but other than that it would let you directly monitor up to 4 event counters at once. You can monitor dcache/icache misses, branch mispredictions, and so on. Really such a module would be good for a lot of people so it'd be great if someone did one, if it hasn't already been. Or it could be made part of the kernel proper.
Can't oprofile be used for that?
 
Last edited by a moderator:
Laurent said:
That's very interesting. This basically means you don't have self-modifying code and no gencode cache capacity, right?
In that case, I wonder if some static compilation could be applied. That'd require some runtime profiling of generated code to identify hotspots that could benefit from heavier optimization.
For small games like Mario 64 the recompilation is effectively static. But larger games tend to swap code modules in and out of memory when you go into different areas of the game.

There is no profiling, all optimizations are applied to every block. What additional optimizations would you make based on profiling data?

Laurent said:
But it's probably useless: I guess in the end HLE tricks will bring more speedups :)
HLE is only used for the RSP/RCP. Every MIPS instruction is executed.
 
Last edited by a moderator:
Laurent said:
I will speak for myself in that case :) Don't many old games/demos from the i386 era use SMC? Since I'm interested in that, it is legitimate to ask for the cost of translation, I guess.
I agree that for ROM-based machines, one doesn't have to really consider that cost (unless it's so heavy it's noticeable).

I have no idea how many old x86 games use self modifying code, but its usage has certainly lowered since the advent of modern cached, pipelined platforms. Ari64 already mentioned that he hasn't seen it legitimately used on the N64.

Even if games perform self-modifying code it doesn't mean that the recompilation cost will weigh you down a lot, because the amount modified per frame tends to be very small. Of course, you need some good techniques for coping with this.

Laurent said:
Can't oprofile be used for that?

I don't know, you tell me. Is it working on Cortex-A8? Would you like to try it and tell us?

Ari64 said:
HLE is only used for the RSP/RCP. Every MIPS instruction is executed.

Given the small amount of 64bit code used by games (and I hear that a lot of it is library code) it could offer a substantial speedup if you're capable to HLE out these sections then recompile the rest of the code as strictly 32bit.
 
Last edited by a moderator:
Back
Top