Re: compiler smarts: register variables and catching exceptions
andrew_nuss@yahoo.com wrote:
I put the register keyword there primarily for effect. Let me be more
explicit.
This is a virtual machine loop, and the four stack pointers is an
exaggeration,
there are only 3 of them, plus an opcode array whose offset doesn't
change.
I'm assuming that the compiler has profiled the switch statement and
seen
that so many of the sp1..sp3 accesses are happening, that it will want
to
use registers for those stacks.
The compiler cannot automagically profile your program. I assume that
your compiler can read profiling results and you supplied them.
As to the issue of function calls and throws, that happens in only a
few of
the switch cases. Most of the cases are just 2 or 3 statements plus
some inline function calls. However, from the compiler's standpoint,
any of the function calls made in any of the switch cases could throw
specialexception, and unfortunately, I need to catch it, clean up the
stacks, and resume processing in many cases. That means that the
values of sp1..sp3 at the time of the function call that throws are
needed
in the catch block, and my guess is that this is easiest for the
compiler
if those values are held in the frame.
main {
// assume that these are used so frequently that
// compiler would choose to put them in registers on its own
// (aside from catch block issues seen below)
register int* sp1 = ...;
register int* sp2 = ...;
register int* sp3 = ...;
register int* bytecodes = ...;
register int cursor = 0;
do {
try {
do {
int opcode = bytecodes[cursor++];
switch (opcode) {
case DUP1: {
// very important for sp1 to be a register
// because this is a common opcode
// even though its only one case in the switch
// and there's only 2 statements here
int temp = *(sp1-1);
*--sp1 = temp;
break;
}
case CALLNATIVE: {
// call a function which could throw special
exception
// this case does not happen frequently and
does
// not need to be fast.
*--sp2 = MyFunctionWhichCanThrow(*sp1++,
*sp1++);
I think the behaviour is undefined in this case. You'd better assign
*sp1++ twice to temporal vars. Recall that the order of evaluation of
function arguments is not determined in C/C++.
break;
}
}
} while (true);
} catch (SpecialException& e) {
...
// manipulate sp1..sp3 as seen in first posting
// does the fact that sp1..sp3 are being used in the catch
block
// mean that registers will not be chosen by the compiler
for them???
// otherwise, the compiler would have to unwind not only
the frame
// but also the complete register state. I just cannot
see how a compiler
// could do this.
}
} while (true);
}
Well, let's assume that the compiler saved these values in memory.
Very fortunately, even if the values were saved, the compiler wouldn't
need to restore them for the DUP1 case: it can use their registers
because no function calls occured between definition of sp1...3 and use
of them in this particular case.
The cost of saving the values is usually very low, because the
processor can immediately proceed to other instructions. I think the
only problem you might have is that your processor doesn't have enough
registers (i86?), and therefore it has to reuse some registers to
compute these 6 vars. Only in this case it will need to restore the
values in the DUP1 case.
Recall that you use 6 long-living int vars in this code: sp1..3,
bytecodes, cursor and opcode. The last of them, 'opcode', would
probably be put in a register because it's used in a switch. On an x86,
some of the others might be saved/restored. On a RISC machine, all the
vars will be assigned different registers. In case that your compiler
knows that DUP1 is the most frequently used part of the switch
statement, it will probably assign sp1 to a register that is not reused
for other vars.
Michael
P.S. Did you see the assembly of this code?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]