Because of the way exceptions are going to work, we need to make sure
that the code emitted for each individual opcode is self-contained,
relative to the system stack. That is to say, when an opcode is done
it can't leave any cruft on the system stack, and it shouldn't expect
there to be any information on the system stack.
The exception system's going to be based on setjmp/longjmp[*], with a
setjmp done just before entering the runloop, and longjmps done when
an exception is thrown. The low-level exception handler will then
unwind the interpreter stacks until it finds an exception handler, at
which point it'll enter the runloop at the point the exception
handler dictates. (The point that the parrot-level exception system
noted that it was supposed to resume at)
This'll generally mean that the system stack'll be pretty mushed, and
the runloop code won't have any guarantees as to what's on the stack,
but execution must continue properly from that point on. So recursive
calls to parrot functions can't recursively use the system stack or
anything, as that'll get unwound by the low-level exception scheme
and Bad Things Will Happen. And we wouldn't want that...
[*] Well, their signal-safe equivalents at least
--
Dan
--------------------------------------"it's like this"-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk
> I'm about to do exceptions, and as such I wanted to give a quick warning
> to everyone who does Odd Things. (Which would be in the JIT, mainly :)
>
> Because of the way exceptions are going to work, we need to make sure
> that the code emitted for each individual opcode is self-contained,
> relative to the system stack. That is to say, when an opcode is done it
> can't leave any cruft on the system stack, and it shouldn't expect there
> to be any information on the system stack.
This would mean, that current jit/i386 is not ok, because it pushes a
parrot Interp* on the stack in Parrot_jit_begin and leaves it there ...
> The exception system's going to be based on setjmp/longjmp[*], with a
> setjmp done just before entering the runloop, and longjmps done when an
> exception is thrown. The low-level exception handler will then unwind
> the interpreter stacks until it finds an exception handler, at which
> point it'll enter the runloop at the point the exception handler
> dictates.
.... but reentering the (jitted) runloop is just like Parrot_jit_begin +
jump to current_pc, so it would be ok.
> ... So recursive
> calls to parrot functions can't recursively use the system stack or
> anything, as that'll get unwound by the low-level exception scheme and
> Bad Things Will Happen. And we wouldn't want that...
I don't see any recursive calls. But anyway, when on (re)entering the
runloop everything gets setup as it ought to, an exception is a noop.
What I'm missing here?
The real problem is different IMHO and we had this already with perl 6
exceptions[1]: resuming after an exception may theoretically happen on
an arbitrary opcode, which might be e.g. in midst of a jitted section,
where parrot registers live in processor registers.
What JIT needs to know is the location of the resume opcode, to mark it
as a jump target properly, so that processor registers can be setup
correctly.
More problems arise here: The exception handler might use parrot
registers, which might live (or better might have lived) in processor
registers, which the longjmp already has destroyed.
So we IMHO need to mark each OP with a flag, if it might throw an
exception and restore all processor registers to parrot registers before
doing this OP.
[1] imcc marks the address of a set_addr OP as branch target, so that
this basic block wouldn't be removed by dead code detection and the
register allocator does know what to do. When a arbitrary opcode can
jump out of the current block and resume elsewhere, the register
allocator can't assign the same registers to these variables.
So we have 3 levels, where we might have troubles:
- JIT: processor registers
- IMCC: parrot registers
- HL: lexicals
leo
That's just a speed hack that will go away when the moment comes.
> > The exception system's going to be based on setjmp/longjmp[*], with a
> > setjmp done just before entering the runloop, and longjmps done when an
> > exception is thrown. The low-level exception handler will then unwind
> > the interpreter stacks until it finds an exception handler, at which
> > point it'll enter the runloop at the point the exception handler
> > dictates.
>
> .... but reentering the (jitted) runloop is just like Parrot_jit_begin +
> jump to current_pc, so it would be ok.
>
> > ... So recursive
> > calls to parrot functions can't recursively use the system stack or
> > anything, as that'll get unwound by the low-level exception scheme and
> > Bad Things Will Happen. And we wouldn't want that...
>
> I don't see any recursive calls. But anyway, when on (re)entering the
> runloop everything gets setup as it ought to, an exception is a noop.
>
> What I'm missing here?
>
> The real problem is different IMHO and we had this already with perl 6
> exceptions[1]: resuming after an exception may theoretically happen on
> an arbitrary opcode, which might be e.g. in midst of a jitted section,
> where parrot registers live in processor registers.
> What JIT needs to know is the location of the resume opcode, to mark it
> as a jump target properly, so that processor registers can be setup
> correctly.
Well, any opcode could be a target, so I suggest to build something like a
section entrance code that given the PC could load the apropiate registers
and jump to the middle of a jitted section. Yes, it will be slow but we are
talking about exceptions here.
> More problems arise here: The exception handler might use parrot
> registers, which might live (or better might have lived) in processor
> registers, which the longjmp already has destroyed.
> So we IMHO need to mark each OP with a flag, if it might throw an
> exception and restore all processor registers to parrot registers before
> doing this OP.
>
Since there isn't any jitted opcode throwing an exception (and all registers
are saved back to parrot register before calling C code), and if there were
any opcode jitted throwing an exception it must save the registers back
before doing so.
> [1] imcc marks the address of a set_addr OP as branch target, so that
> this basic block wouldn't be removed by dead code detection and the
> register allocator does know what to do. When a arbitrary opcode can
> jump out of the current block and resume elsewhere, the register
> allocator can't assign the same registers to these variables.
>
> So we have 3 levels, where we might have troubles:
> - JIT: processor registers
> - IMCC: parrot registers
> - HL: lexicals
>
> leo
Daniel Grunblatt.
> On Thursday 14 November 2002 05:14, Leopold Toetsch wrote:
>>What JIT needs to know is the location of the resume opcode, to mark it
>>as a jump target properly, so that processor registers can be setup
>>correctly.
> Well, any opcode could be a target, so I suggest to build something like a
> section entrance code that given the PC could load the apropiate registers
> and jump to the middle of a jitted section. Yes, it will be slow but we are
> talking about exceptions here.
There must be some code, that installs the exception handler. This code
has the address of the exception handler, which JIT has to know too.
Being prepared to enter at arbitrary places in JIT would inhibit
processor register usage totally, or wouldn't be any win.
>>So we IMHO need to mark each OP with a flag, if it might throw an
>>exception and restore all processor registers to parrot registers before
>>doing this OP.
> Since there isn't any jitted opcode throwing an exception ...
div by zero, modulo (div currently only PPC but...)
> (and all registers
> are saved back to parrot register before calling C code),
.... which I want to avoid for callee saved registers ...
> and if there were
> any opcode jitted throwing an exception it must save the registers back
> before doing so.
So, it would be useful to know, which op code might throw an exception.
> Daniel Grunblatt.
leo
Either I didn't make my self clear (sorry) or my understanding of exceptions
is wrong ... probably both.
When, say, div throws an exception it will longjmp to the handler, that
handler will know where to continue the execution of the program, right? so
after doing its job it will have to call a function that given a PC will load
the cpu registers as needed and jump into the jitted code again but if there
was no exception the jitted code executes as usual.
> >>So we IMHO need to mark each OP with a flag, if it might throw an
> >>exception and restore all processor registers to parrot registers before
> >>doing this OP.
> >
> > Since there isn't any jitted opcode throwing an exception ...
>
> div by zero, modulo (div currently only PPC but...)
>
> > (and all registers
> > are saved back to parrot register before calling C code),
>
> ... which I want to avoid for callee saved registers ...
>
> > and if there were
> > any opcode jitted throwing an exception it must save the registers back
> > before doing so.
>
> So, it would be useful to know, which op code might throw an exception.
Certainly.
>
> > Daniel Grunblatt.
>
> leo
Daniel Grunblatt.
Can any opcode be a resume target without knowing that it is a resume target?
If yes, we have a nasty time being a JIT.
If we know all resume targets in advance (care of the compiler or assembler
labels, even if all resume targets includes "all opcodes that follow any
opcode that could throw an exception") then I think life is a lot easier.
[which is probably what the bits I've snipped were saying]
In my mind there seem to be two classes of things:
ops that could throw exceptions
ops that could be the resume target of exceptions
I'm assuming that we can only resume on op boundaries.
(so a division by zero exception has to resume after the op.
er. what happens on some sort of PMC fetch exception. Can it resume
internally?
)
> > There must be some code, that installs the exception handler. This code
> > has the address of the exception handler, which JIT has to know too.
> > Being prepared to enter at arbitrary places in JIT would inhibit
> > processor register usage totally, or wouldn't be any win.
> >
>
> Either I didn't make my self clear (sorry) or my understanding of exceptions
> is wrong ... probably both.
>
> When, say, div throws an exception it will longjmp to the handler, that
> handler will know where to continue the execution of the program, right? so
> after doing its job it will have to call a function that given a PC will load
> the cpu registers as needed and jump into the jitted code again but if there
> was no exception the jitted code executes as usual.
This sounds horribly like the C rule of don't rely on automatic variables
after the return from longjump.
No. I don't have an answer.
Nicholas Clark
> On Thu, Nov 14, 2002 at 11:23:04AM -0300, Daniel Grunblatt wrote:
> Can any opcode be a resume target without knowing that it is a resume target?
> If yes, we have a nasty time being a JIT.
The question is "without knowing". I think the resume address is known
(somewhere at least) because the exception handler has to be set up.
> In my mind there seem to be two classes of things:
>
> ops that could throw exceptions
> ops that could be the resume target of exceptions
>
> I'm assuming that we can only resume on op boundaries.
I think so.
> (so a division by zero exception has to resume after the op.
Or could retry the op (as was proposed in perl6-language) after
correcting the "problem".
> er. what happens on some sort of PMC fetch exception. Can it resume
> internally?
Unlikely, either the op is repeatable or not (because it already had
side effects)
>>When, say, div throws an exception it will longjmp to the handler, that
>>handler will know where to continue the execution of the program, right? so
>>after doing its job it will have to call a function that given a PC will load
>>the cpu registers as needed ...
This code has to be in JIT already (with probably some helper function
outside), so it could be rather complicated, but it is doable of course.
> This sounds horribly like the C rule of don't rely on automatic variables
> after the return from longjump.
>
> No. I don't have an answer.
But I have a proposal:
- Normal runloops don't have a problem with longjmp
- JIT could have it's own low level exception handler:
* gets jumped to, so registers are still ok
* saves processor registers to parrots
* then longjmps to parrot handler
As we already seem to need to restore processor registers on resume, we
could do the reverse thing on exception too.
> Nicholas Clark
leo
> - Normal runloops don't have a problem with longjmp
>
> - JIT could have it's own low level exception handler:
> * gets jumped to, so registers are still ok
I am not clear how this works if the exception is triggered in a C function
called from JIT code.
> * saves processor registers to parrots
> * then longjmps to parrot handler
>
> As we already seem to need to restore processor registers on resume, we
> could do the reverse thing on exception too.
>
>
> >Nicholas Clark
>
> leo
--
Jason
> But I have a proposal:
>
> - Normal runloops don't have a problem with longjmp
>
> - JIT could have it's own low level exception handler:
What happens when C code called from the JIT generated code generates an
exception ?
> * gets jumped to, so registers are still ok
> * saves processor registers to parrots
> * then longjmps to parrot handler
--
Jason
> - JIT could have it's own low level exception handler:
> * gets jumped to, so registers are still ok
> * saves processor registers to parrots
> * then longjmps to parrot handler
I didn't finish my response...
The way I have thought this would be done (given C opcode functions raising
exceptions) is to spill parrot registers back into the interpreter structure
from hardware registers before calling opcode functions that might raise
exceptions using longjmp. Spill here means we update the interpreter structure
registers, but consider the CPU register values still valid if the opcode does
not raise an exception.
JIT code that needs to raise an exception can do the same thing as C code, call
the exception raising function that does a longjmp.
The runops loop would use setjmp to catch the exception and will do one of at
least two things:
Determine that the exception resumes control at an op that can be safely
jumped to in JIT generated code. In other words the op is not in the middle of
a section of code for which the JIT code assumes a certain register allocation.
Or
Find that the execution resumes control somewhere that is not safe to jump to
in the JIT code (or has not been JIT compiled), and passes control to the
interpreter runops loop.
The remaining issue is then determining when it is again safe to resume jit
execution so that control can be passed back to the JIT code. I have a few
ideas on doing this, but I'd like to hear what folks have to suggest. As has
been pointed out a few times before, exceptions and the restart ops present the
same problem.
The fact is that the above complication will be necessary once we start dealing
with multiple non-contiguous bytecode segments, as I do not expect one will JIT
compile every piece of code because of the inefficiency of doing that (unless
we make the JIT code less specialized than it is on at least IA-32/x86, where
addresses of registers and the interpreter pointer are embedded in the
generated code).
--
Jason
> On Thu, Nov 14, 2002 at 04:28:00PM +0100, Leopold Toetsch wrote:
>
>
>>But I have a proposal:
>>
>>- Normal runloops don't have a problem with longjmp
>>
>>- JIT could have it's own low level exception handler:
>>
>
> What happens when C code called from the JIT generated code generates an
> exception ?
I was thinking of something like this:
internal_exception(reason) {
if (JIT_run_loop) /* or better setup a call vector to the handler */
jit_exception(reason); /* pointing either here */
else
longjmp(..); /* or directly here */
}
jit_exception(reason) {
save_processor_regs_to_parrot();
longjmp(..);
}
jit_exception would need to know the current interpreter for jit_info,
but this is not that problem, the exception handler needs this too, to
find the resume code. And the interpreter* is on the stack, which is in
the jmp_buf ...
But above is only needed, if there are callee saved registers around
which hold parrot register values not already saved. So currently not,
because there are no unsaved registers, when calling external code and
jitted OPs currently don't throw exceptions.
leo
> On Thu, Nov 14, 2002 at 04:28:00PM +0100, Leopold Toetsch wrote:
> I didn't finish my response...
>
> The way I have thought this would be done (given C opcode functions raising
> exceptions) is to spill parrot registers back into the interpreter structure
> from hardware registers before calling opcode functions that might raise
> exceptions using longjmp.
Rather inefficient, as probably every function may throw an exception
(i.e. a PMC called with a wrong vtable method), but of course a doable
approach.
> Find that the execution resumes control somewhere that is not safe to jump to
> in the JIT code (or has not been JIT compiled), and passes control to the
> interpreter runops loop.
I'd rather restore registers properly. We know per section, which
registers are used, so it shouldn't be too hard.
> ... As has
> been pointed out a few times before, exceptions and the restart ops present the
> same problem.
Yes, current restart code (i386) would break, when registers are saved
through external calls.
> The fact is that the above complication will be necessary once we start dealing
> with multiple non-contiguous bytecode segments, as I do not expect one will JIT
> compile every piece of code because of the inefficiency of doing that (unless
> we make the JIT code less specialized than it is on at least IA-32/x86, where
> addresses of registers and the interpreter pointer are embedded in the
> generated code).
restarting JIT, exceptions or multiple segments are all similar. WRT
inefficiency: current JIT code is slow for external code - mainly when
PMCs are involved (I have some ideas to get this a lot faster) and JIT
code is produced at program load time. The latter could be done in IMCC,
which has all the info (basic blocks ...) ready, so this info could be
passed on to build_asm, which would be a lot simpler and somehow faster.
I do expect to JIT compile everything.
leo
> But above is only needed, if there are callee saved registers around
> which hold parrot register values not already saved. So currently not,
> because there are no unsaved registers, when calling external code and
> jitted OPs currently don't throw exceptions.
If the JIT allocates any parrot register contents to caller-save registers,
those registers must be saved to backing store-somewhere. On IA-32 the only
place those registers can be spilled is RAM, either on the stack or on the
heap. In this case, you basically have the scheme I described.
If the JIT allocates any parrot register contents to callee-save registers
(which we use strictly as such), and calls an external function that raises an
exception, you cannot restore the contents of those registers to Parrot
registers after the external function raises an exception because you do not
know where or how the compiler has saved the registers.
--
Jason
> The question is "without knowing". I think the resume address is known
> (somewhere at least) because the exception handler has to be set up.
If I understand it correctly, the way recent Linux handles page faulting in
the kernel may be of use. It takes advantage of the ELF format to generate
an list of fixup routines, one per place that could fault, ordered by the
fault address. If the instruction at that address faults, the code called by
the MMU searches the list, to find the correct fixup code. The fixup code is
run, and execution resumes.
This makes fixup slower than inline explicit check code, but fixup happens so
rarely that the removal of lots of instances of explicit check code is a net
win.
I wonder if we could use the same idea in the JIT, with out of band exception
fixup code (or tables, or whatever) that the JIT exception handler uses to
restart. I'm making the assumption that exceptions can happen in many places,
but rarely do, so we can ill afford to make the direct code slower to cope
with them, and that it doesn't matter if it takes a lot of effort to handle
them when they happen.
Nicholas Clark
--
perl1 better than perl? http://www.perl.org/advocacy/spoofathon/
> If the JIT allocates any parrot register contents to callee-save registers
> (which we use strictly as such), and calls an external function that raises an
> exception, you cannot restore the contents of those registers to Parrot
> registers after the external function raises an exception because you do not
> know where or how the compiler has saved the registers.
Yep, you are right - I did miss this point sometimes. We have to do a
_save_registers before calling code, that might throw an exception.
leo
Excuse me for butting in ... But how are the parameters to a C code being
passed .. I assume that would use a stack or register ... So it is always
possible for a C code to break the ability to restore registers if they
are stored in the stack ....
I guess pushall,call,popall would be the easiest approach ... let the
programmer writing C code handle the rest of the mess ... You can't
prevent SEGFAULTs in external calls , just let the programmer handle
them...
Speaking about debugging calls from JIT'd code ... Does parrot JIT
generate a debugging segment ? like a dwarf2 segment so that we can
debug with gdb ? ... (I hate it when I see 200 ?? in a backtrace).
I'm a man of simple ideas ... :-)
Gopal
--
The difference between insanity and genius is measured by success
> If memory serves me right, Leopold Toetsch wrote:
>
>>Yep, you are right - I did miss this point sometimes. We have to do a
>>_save_registers before calling code, that might throw an exception.
>>
>
> Excuse me for butting in ... But how are the parameters to a C code being
> passed .. I assume that would use a stack or register
stack
> I guess pushall,call,popall would be the easiest approach
pusha/popa is overkill. The called functions always save bp and bx,di,si
when used. ax is the return value, remaining is dx (cx is used by
shifts) - i386 of course.
> Speaking about debugging calls from JIT'd code ... Does parrot JIT
> generate a debugging segment ? like a dwarf2 segment so that we can
> debug with gdb ? ... (I hate it when I see 200 ?? in a backtrace).
That would be a really nice feature. Can we - and how - handle over
debug info to a running gdb?
> I'm a man of simple ideas ... :-)
> Gopal
leo
> pusha/popa is overkill. The called functions always save bp and bx,di,si
> when used. ax is the return value, remaining is dx (cx is used by
> shifts) - i386 of course.
Ok ... push_necessary() ;-) ...
> > Speaking about debugging calls from JIT'd code ... Does parrot JIT
> > generate a debugging segment ? like a dwarf2 segment so that we can
> > debug with gdb ? ... (I hate it when I see 200 ?? in a backtrace).
>
>
> That would be a really nice feature. Can we - and how - handle over
> debug info to a running gdb?
It is possible ... JIT generated code looks just like loaded code to
gcc ... Typically gdb should only need access to a symfile to correctly
allow debugging ... So an .o file of the JIT'd code should be correctly
generated with all the trimmings.
$ gdb parrot
(gdb) run -debug=dwarf2 --break __main__ Foo.pbc
(gdb) call Parrot_debug_make_syms()
(gdb) add-symbol-file Foo.o
Reading symbols from Foo.o
(gdb) frame
#0 __main__ at Foo.py:5 (HINT: where's the python compiler :-)
The trick here is to use `gas' or the gnu assembler to generate the
debugging info from the assembly dump you provide ... For example a
function would be dumped as...
..string "Func"
..byte 0x13 ; METHOD_RETVAL
..byte 0x01 ; _public
..byte 0x00 ; _none (not virtual)
..long .L_TYPE_00000 - .L_debug_info_b ; return type at label
.....
This would mean that in debug mode the JIT will have to turn off all
the optimisations and that the packfile contains a linenumber table
or something similar ... (maybe even the whole source program too..
like gcc does ..)
This is just an idea ... I'm too busy in DotGNU to try this out now ..
And we have no JIT to do this stuff ... just an X86 code unroller for
simple instructions sort of like the u-v pipelines in pentiums .. the
fast x86 run or slow interpret according to instruction complexeties ..
There this idea would mean gdb has to debug both the modes together....
Hth,
You can find the complete examples of how the jit debugging features
work in the mono tarball (mono/doc directory): the above was a
partial cut&paste with s/mono/parrot/ :-)
We have also an implementation of the symbol file writer in mono/jit/debug*
that may be helpful to look at.
lupus
--
-----------------------------------------------------------------
lu...@debian.org debian/rules
lu...@ximian.com Monkeys do it better
> On 11/15/02 Gopal V wrote:
>
>>It is possible ... JIT generated code looks just like loaded code to
>>gcc ... Typically gdb should only need access to a symfile to correctly
>>allow debugging ... So an .o file of the JIT'd code should be correctly
>>generated with all the trimmings.
>>
>>$ gdb parrot
>>(gdb) run -debug=dwarf2 --break __main__ Foo.pbc
>>(gdb) call Parrot_debug_make_syms()
>>(gdb) add-symbol-file Foo.o
>>Reading symbols from Foo.o
>>(gdb) frame
>>#0 __main__ at Foo.py:5 (HINT: where's the python compiler :-)
>>
>>The trick here is to use `gas' or the gnu assembler to generate the
>>debugging info from the assembly dump you provide ... For example a
>>function would be dumped as...
>>
>
> You can find the complete examples of how the jit debugging features
> work in the mono tarball (mono/doc directory): the above was a
> partial cut&paste with s/mono/parrot/ :-)
>
> We have also an implementation of the symbol file writer in mono/jit/debug*
> that may be helpful to look at.
Fullqouting here really good news. Actually while working on JIT I had
just the same idea, and did read a lot of gdb stuff - how to get debug
info in dynamically.
I'll check out mono.
Thanks Gopal & Paolo
leo
Same old lupus .... I never realized I had a wolf pack hunting me :-)
> the above was a partial cut&paste with s/mono/parrot/ :-)
But don't act like you invented it .... Kaffe had one before you thought
about mono JIT ...
http://www.kaffe.org/doc/kaffe/FAQ.xdebugging
I just saved some typing by cut-pasting what I had in my box ....
And don't bother to thank me for introducing the "debug jit" so that you
could show up with "Mono has this and more" banner....
The idea probably predates kaffe, too. Anyway, I didn't say I had the
idea, but the implementation. Quoting what you dropped from my mail, I
said we had the complete examples and:
> We have also an implementation of the symbol file writer in
> mono/jit/debug* that may be helpful to look at.
If you think you have novel ideas in the JIT (or in the intepreter) space,
you're probably just deluding yourself.
> http://www.kaffe.org/doc/kaffe/FAQ.xdebugging
>
> I just saved some typing by cut-pasting what I had in my box ....
> And don't bother to thank me for introducing the "debug jit" so that you
> could show up with "Mono has this and more" banner....
I pointed at the mono implementation simply because it is more complete
and flexible than the kaffe one. kaffe only does the stabs format
(and it outputs only the info for the code). Mono has the code that
allows you to access locals and arguments in a stack frame and inspect
the fields of the runtime-created types. Mono can also output symbol
files in both stabs and dwarf format: the dwarf format doesn't have the
limitation of stabs regarding line number info, for example, and it
makes it possible to output debug info also for optimized code.
BTW: the mono implementation is not mine, I just started it and then
Martin Baulig filled the holes and implemented the dwarf stuff.
If you can find a better implementation than mono's, I'd like to know
about it, too:-) Until then, it's just a waste of people's time to
provide limited information or to point to a limited implementation of
the idea ;-)
<sarcasm>
Oh yes .... I said that "debuggable JIT" is one of my very own personal
ingienous ideas .
</sarcasm>
This list for people who are interested in parrot and its VM.... Which
is why I posted that info .. But I cannot see the same motive behind your
mail ...
> If you can find a better implementation than mono's, I'd like to know
> about it, too:-)
Guess I'll have to wait until the Parrot guys finish ....
*sigh*, do you Ximian people never quit stalking me ? ...
NOREPLY,