extending libgcc's exception delivery path
Godmar Back
gback@cs.utah.edu
Fri Dec 31 23:54:00 GMT 1999
>
> Bear in mind that the stiffness of the resulting code is less able to
> be optimized than if you preserve the flexibility of the code by using
> the midend. The best optimization happens when you choose a better
> algorithm, or a better datastructure, or a new philosophy. By
> prewiring this too early, you loose that ability to improve the code.
> Leading to less optimal code in the mid and longer term.
>
> Or, try multiple ways of doing it, see what the results are what the
> performance is, and then go with the `best one'.
>
I don't think we disagree. I think that *requiring* to wrap functions
in try/finally and closing out all other possibilities upfront, without
having evaluated such possibilities, should be avoided.
> I thought you expressed yourself perfectly.
>
> You're lucky, or you just solve a subset problem. Getting it to kinda
> work isn't too hard. Can you solve the problem when gcc optimizes the
> prologue and epilogue to death and saves registers in the mmx
> registers because someone found it produced faster code and eliminates
> the frame pointer? No.
>
Okay, I'm dense.
I was under the impression that unwinding information is stored and
processed on a frame-by-frame basis, and that it is complete.
So no matter what happens to the prologue/epilogue in the invoked
function, and no matter what registers are used, dwarf should be able
to unwind it? Then why should I not be able to skip this one frame?
gcc isn't optimizing my assembly code trampoline, after all.
The reason I don't understand what you're saying is this:
If my assembly function had been generated by a compiler (that's
possible, isn't it?), then would it also have been an unknowable
problem for that compiler to emit the correct dwarf2 information?
I think not; so no matter what that information would be (it's constant,
after all), I should be able to emulate its effect when I'm invoked at
unwinding time; it cannot possibly depend on what's done in the callee.
Don't get me wrong; I don't insist on using asm trampolines of any kind.
In fact, we'd be greatful for suggestions on how to avoid them. I just
want to be sure I understand what you're getting at.
> > In your scheme, how would I communicate callee-saved registers to a
> > precompiled caller?
>
> Your question isn't this, it is, what if in my precompiled code I do
> X, and X must be undone in EH situations. The answer is you place a
> region that starts after you have done X, extending until just before
> you undo X, that has a handler that undoes X and then rethrows.
>
> For example, the X can be saving a callee-saved register in your JIT
> code. Undoing it would be to move the register from the save location
> on the stack back into the register. For regions that are the same
> (same begin and same end), you collapse the actions together.
>
Okay, fair enough. I understand your abstraction and agree that
it applies.
It seems horribly complicated to require a catch/rethrow for such
simple "X" as restoring a callee-saving register, but I agree that
it would cover that case.
>
> After you do all of this, you will notice the large number of these
> things that you have to generate, the space they take, the way they
> kill the instruction cache, and you will say, if only I put them out
> into the .data segment, I could improve this... then you would
> implement the dwarf generation, as it handles this with the advantages
> describe above. It is just a quality of implementation detail. The
> first implementation can be less efficient. Remember, you can't have
> a second implementation until after you have a first. Anyway, just
> trying to forshadow a little.
>
Optimizing for instruction cache pressure may not always be an overriding
priority. I would claim that saving memory on the heap can be as
important, especially for garbage collected languages such as Java.
In addition, it's not obvious to me that they would kill the instruction
cache, if they're properly arranged. After all, that code is only executed
if an exception is thrown. This is similar to moving error handling
code to the end of a function, which has been shown to be beneficial.
The current implementation takes 1 word per jitted method (to store
the size of the local variables) and it takes ca. 16 lines of
C code without branches to restore the registers. I'm unlikely to
prefer implementing dwarf generation to this. Keep in mind that
the only action "X" to be undone by jitted code is to restore callee-saved
registers, which are always eagerly saved at a known, constant offset.
This is also not an investment anybody would hang on to should the need
for more complicated mechanisms arise.
I understand though that you want me to look at what may be coming.
I guess my position is to tackle easy problems with simple solutions,
and resort to more complex solutions only if the problems get harder
and cannot be simplified.
I concede that you may have a point if you're looking at a more advanced
jit. To add some anecdotal evidence, however, the designers of the currently
fastest JVMs on the market (Compaq's FastVM, "fastest" according the SpecJVM
results) decided against using an interpreted language such as dwarf to
implement their unwinding. Instead, they actually generate native code
on the side to undo such simple Xs as restoring registers.
- Godmar
[*] http://www.digital.com/java/FastVM.html
More information about the Gcc
mailing list