I have an old C++ project I made a while back. Well, it is a CPU emulator. Whenever a CPU fault happens(such as divide by zero, or debug breakpoint interrupt, etc) in my code it just does a throw
and in my main loop I have something like this:
try{
*(uint32_t*)&op_cache=ReadDword(cCS,eip);
(this->*Opcodes[op_cache[0]])();
eip=(uint16_t)eip+1;
}
catch(CpuInt_excp err){
开发者_Go百科err.code&=0x00FF;
switch(err.code){
case 0:
case 1: //.....
Int16(err.code);
break;
default:
throw CpuPanic_excp("16bit Faults",(err.code|0xF000)|TRIPLE_FAULT_EXCP);
break;
}
}
And a simple opcode example(pulled out of thin air)
if(**regs16[AX]==0){
throw CpuInt_excp(0); //throw divide by zero error
}
What this code basically does is just reads an opcode and if an exception occurred, then call the appropriate interrupt(in the CPU, which just changes the EIP)
Well, this being in the main loop, the try{}catch{}
overhead really adds up. This isn't a premature optimization, I profiled it and gcc's exception helper functions(without even doing any faults and thus no throws) and the helper functions took up over 10% of the total execution time of a long running emulated program.
So! What would be the best way of replacing exceptions in this case? I would prefer not to have to keep track of return values because I already have a ton of code written, and because keeping track of them is really difficult when functions get really deep.
You haven't shown your loop, but I'm guessing in pseudocode it's:
while (some_condition) {
// Your try..catch block:
try {
// Do an op
}
catch (CpuInt_excp err) {
// Handle the exception
}
}
You could move the try..catch
out a level:
done = false;
while (!done) {
try {
while (some_condition) {
// Do an op
}
done = true;
}
catch (CpuInt_excp err) {
// Handle the exception
}
}
There I've included two loops, because I assume that if an exception occurs you want to be able to carry on (hard to tell without knowing what you're doing in Int16
, but I think you're allowing carrying on after non-panic exceptions). Naturally if you don't need to carry on, you only need one loop.
The outer loop just restarts things after the exception. It can check the same condition as the inner loop if that condition is not expensive to check (e.g., it's a program counter or something), or it can have a flag as in the above if the condition is expensive to check.
How about moving the try/catch out of your inner loop? It would take more bookkeeping in the catch, and some additional overhead getting back into your innerloop without starting from the top, but you would pay only when an exception actually fired rather than all of the time.
It looks like you're recording the CPU state in member variables, since your opcode methods don't take any parameters. At the risk of stating the obvious, why not do the same for the exception state? Then you won't have to deal with a return value. Instead, you would just need the opcode functions to set the exception state, return, and let the main loop check the exception state.
精彩评论