* A Working (but Minimal) JIT
@ 2010-10-22 4:29 Noah Lavine
2010-10-22 21:46 ` Phil
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Noah Lavine @ 2010-10-22 4:29 UTC (permalink / raw)
To: guile-devel
Hello all,
After not emailing for a while, I have some good news: a JIT engine is working!
The current version is as minimal as possible. It can only JIT a
function that does nothing and returns 0. And it's only activated by
the 'mv-call' VM instruction. Here's how I've been testing it:
> (define (return-0) 0)
> (define (call-it) (return-0) 0)
> (call-it)
0
That last 0 is returned by JITed code.
The patch against master contains a lot of uninteresting things in
addition to the few interesting ones, so I've put the whole repository
on GitHub to make getting it easier. It's at
git@github.com:noahl/guile-jit.git .
The biggest change since the last version is that I switched from
Lightning to libjit. This was mostly because I realized that if I was
going to use Lightning, I would need to implement a register
allocator, and seemed like a bad idea when libjit already had one.
libjit also solved a memory allocation problem which had been causing
trouble in the Lightning version, and in general has a very
easy-to-use interface, so I think this is a good way to go.
(You'll probably need to configure Guile with '-ljit' to build it,
assuming you have libjit installed in standard include and library
paths. The people from the libjit mailing list recommended using their
git version, which you can find at
http://git.savannah.gnu.org/cgit/dotgnu-pnet/libjit.git, rather than
the latest release.)
I also changed the interface between JITed code and compiled code a
bit, in a way that I think makes more sense, but probably neither way
I've tried is optimal.
I hope this is the sort of JIT engine that you might want to include
into Guile. I think it more or less follows Ludo's plan for
compilation. As I see it, there are now two big tasks to be undertaken
before that could happen, and they're independent.
First, we would need to figure out how to integrate this into the VM
more. Right now it's only activated in the 'mv-call' instruction, but
it should be activated by all of the instructions. Also, the calling
method looks pretty ugly to me now. I'd like to find a way to smooth
it out, but I'm not sure how. Finally, this version uses a five-word
representation for procedures, but it might be possible to get it back
down to four.
Second, the compiler would need to be extended to handle more VM
opcodes. This is a task that could be done incrementally. The compiler
is basically a big switch statement that does each opcode in turn, and
it has the ability to give up at any point if it sees an opcode it
doesn't know how to translate, so opcodes can be added one at a time.
(I would actually like to talk about an alternate way to do that, but
that's a good conversation to have after people decide that the
general design is what Guile needs.)
So, this is a possible way to get a JIT engine in Guile. What do
people think of it?
Noah Lavine
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-22 4:29 A Working (but Minimal) JIT Noah Lavine
@ 2010-10-22 21:46 ` Phil
2010-10-27 21:17 ` Ludovic Courtès
2010-10-27 21:10 ` Ludovic Courtès
2010-11-20 13:37 ` Andy Wingo
2 siblings, 1 reply; 15+ messages in thread
From: Phil @ 2010-10-22 21:46 UTC (permalink / raw)
To: Noah Lavine; +Cc: guile-devel
On Thu, Oct 21, 2010 at 11:29 PM, Noah Lavine <noah.b.lavine@gmail.com> wrote:
> So, this is a possible way to get a JIT engine in Guile. What do
> people think of it?
General question for the list: Have there already been debates on this
list about doing native compilation all the time like a lot of Common
Lisps & Schemes?
Either way, I'm glad to see some progress in this area, great work!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-22 4:29 A Working (but Minimal) JIT Noah Lavine
2010-10-22 21:46 ` Phil
@ 2010-10-27 21:10 ` Ludovic Courtès
2010-10-27 22:53 ` Noah Lavine
2010-11-20 13:37 ` Andy Wingo
2 siblings, 1 reply; 15+ messages in thread
From: Ludovic Courtès @ 2010-10-27 21:10 UTC (permalink / raw)
To: guile-devel
Hello Noah!
Noah Lavine <noah.b.lavine@gmail.com> writes:
> After not emailing for a while, I have some good news: a JIT engine is working!
Woow, neat! :-)
> The biggest change since the last version is that I switched from
> Lightning to libjit. This was mostly because I realized that if I was
> going to use Lightning, I would need to implement a register
> allocator, and seemed like a bad idea when libjit already had one.
> libjit also solved a memory allocation problem which had been causing
> trouble in the Lightning version, and in general has a very
> easy-to-use interface, so I think this is a good way to go.
Interesting. Dealing with end-of-buffers situation is indeed tricky
with lightning, and register allocation is lacking (I thought this
wouldn’t necessarily be a problem because we can do a reasonable job
with a fixed set of statically allocated registers.)
> First, we would need to figure out how to integrate this into the VM
> more. Right now it's only activated in the 'mv-call' instruction, but
> it should be activated by all of the instructions.
I think it’s OK to jit just at call time at first. There could be a
call counter on procedures, which would allow you to determine whether
it’s worth jitting (i.e., only jit a procedure after it’s been call at
least N times.)
> Second, the compiler would need to be extended to handle more VM
> opcodes.
My plan was to use macro magic to turn each ‘VM_DEFINE_INSTRUCTION’ into
a function definition, except for the ‘call’ instructions and similar.
Then all the jit’d code would do is call these functions, so jitting
would be quite simple. Still, it’d remove one layer of interpretation,
which could lead to performance gains.
Then instructions could gradually be rewritten in libjit assembly, but
that takes time.
> So, this is a possible way to get a JIT engine in Guile. What do
> people think of it?
I haven’t looked at the code yet, but it surely sounds interesting to
me!
I’m looking forward to the next news bulletin. ;-)
Thanks,
Ludo’.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-22 21:46 ` Phil
@ 2010-10-27 21:17 ` Ludovic Courtès
0 siblings, 0 replies; 15+ messages in thread
From: Ludovic Courtès @ 2010-10-27 21:17 UTC (permalink / raw)
To: guile-devel
Hello!
Phil <theseaisinhere@gmail.com> writes:
> On Thu, Oct 21, 2010 at 11:29 PM, Noah Lavine <noah.b.lavine@gmail.com> wrote:
>
>> So, this is a possible way to get a JIT engine in Guile. What do
>> people think of it?
>
> General question for the list: Have there already been debates on this
> list about doing native compilation all the time like a lot of Common
> Lisps & Schemes?
I’m open to both JIT and AOT compilation, FWIW.
Andy looked at AOT compilation. There are tools that would be helpful
for that, like Sassy, which could lead to elegant code.
But then it’s probably more work than JIT, less portable, and it’d be a
big change from the user POV, whereas JIT could be completely
transparent (you’d keep fiddling with your .scm and .go files the usual
way.)
Thanks,
Ludo’.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-27 21:10 ` Ludovic Courtès
@ 2010-10-27 22:53 ` Noah Lavine
2010-11-02 22:51 ` Ludovic Courtès
0 siblings, 1 reply; 15+ messages in thread
From: Noah Lavine @ 2010-10-27 22:53 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: guile-devel
Hello!
> Interesting. Dealing with end-of-buffers situation is indeed tricky
> with lightning, and register allocation is lacking (I thought this
> wouldn’t necessarily be a problem because we can do a reasonable job
> with a fixed set of statically allocated registers.)
That seems true, yes. Given that most of the instructions won't need
to be translated, it's possible that switching back over in the middle
of the project wouldn't be that hard. (I have a partially-completed
Lightning JIT to start from, too.)
> I think it’s OK to jit just at call time at first. There could be a
> call counter on procedures, which would allow you to determine whether
> it’s worth jitting (i.e., only jit a procedure after it’s been call at
> least N times.)
That sounds good. I think it should probably be activated by all of
the 'call' instructions, with a counter on procedures.
There is also something that I was trying to figure out but couldn't -
how does code from the REPL make its way into the VM? Specifically,
what entry point should I modify if I want REPL code to be JITted
automatically? (Is it one of the call instructions other than
'mv-call', or a call to a 'vm-*-engine' function?) That would make
testing this a lot easier.
> My plan was to use macro magic to turn each ‘VM_DEFINE_INSTRUCTION’ into
> a function definition, except for the ‘call’ instructions and similar.
> Then all the jit’d code would do is call these functions, so jitting
> would be quite simple. Still, it’d remove one layer of interpretation,
> which could lead to performance gains.
>
> Then instructions could gradually be rewritten in libjit assembly, but
> that takes time.
That makes sense.
I have an alternate idea about how to do this, but please bear with
me, because it would be a little strange.
I could certainly rewrite the important instructions with libjit,
however, this would result in duplicated code, which is not ideal. I
was wondering if you would be interested in coming up with a way to
write them once so that they could be expanded to either
directly-runnable C code or libjit calls. (Libjit basically implements
a subset of C, so I don't think this would be too difficult.)
I was also wondering (and here's where the build process would get
weird) if this language could be translated with Guile. The goal would
be to wind up with a situation where Guile has the ability to take
some description of low-level machine operations and translate them
either into regular C code or into libjit calls. The reason is that it
would be a small step from there to building a libjit-based assembler
directly into Guile, and then you've got the ability to compile things
into machine code using only Scheme. (You could, for instance, then
write a custom compiler for regular expressions that didn't go through
the regular Guile VM instructions but just jumped to machine code.
That might be really, really fast. You could also then start writing
optimizations of Guile code in Guile.)
The trouble would be that then Guile would be used to build Guile. It
would still be possible to have a version which was completely C code,
but that wouldn't be the source version. I know this would be moving
in a strange direction, and I would understand if you thought this was
a bad idea, but I think it would have have interesting benefits.
> I’m looking forward to the next news bulletin. ;-)
Thanks! I'll work on that. :-)
Noah
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-27 22:53 ` Noah Lavine
@ 2010-11-02 22:51 ` Ludovic Courtès
0 siblings, 0 replies; 15+ messages in thread
From: Ludovic Courtès @ 2010-11-02 22:51 UTC (permalink / raw)
To: guile-devel
Hi Noah!
Noah Lavine <noah.b.lavine@gmail.com> writes:
> There is also something that I was trying to figure out but couldn't -
> how does code from the REPL make its way into the VM?
It’s compiled with ‘compile’ from (system base compile), from ‘scheme’
to ‘objcode’ language, then run (unless the ‘interp’ REPL option is set,
in which case code is eval’d). See ‘repl-compile’ & co.
> I could certainly rewrite the important instructions with libjit,
> however, this would result in duplicated code, which is not ideal. I
> was wondering if you would be interested in coming up with a way to
> write them once so that they could be expanded to either
> directly-runnable C code or libjit calls.
Of course I’d be interested in such a beast! :-)
> (Libjit basically implements a subset of C, so I don't think this
> would be too difficult.)
Oh, do you have pointers to the relevant part of the libjit manual?
> I was also wondering (and here's where the build process would get
> weird) if this language could be translated with Guile.
Of course, that’s the Holly Grail. :-)
Perhaps CGEN <http://sourceware.org/cgen/> could even come into play
somehow. ;-)
But it’s tricky. You’d have to devise a language that’s both
sufficiently expressive to implement all the instructions and
sufficiently simple to make compilation to C or libjit doable.
[...]
> The trouble would be that then Guile would be used to build Guile.
That can hopefully be worked around by checking in a copy of the
generated C code, for instance.
Thanks,
Ludo’.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-10-22 4:29 A Working (but Minimal) JIT Noah Lavine
2010-10-22 21:46 ` Phil
2010-10-27 21:10 ` Ludovic Courtès
@ 2010-11-20 13:37 ` Andy Wingo
2010-11-28 20:56 ` Noah Lavine
2010-11-28 20:58 ` Noah Lavine
2 siblings, 2 replies; 15+ messages in thread
From: Andy Wingo @ 2010-11-20 13:37 UTC (permalink / raw)
To: Noah Lavine; +Cc: guile-devel
Hi,
On Fri 22 Oct 2010 06:29, Noah Lavine <noah.b.lavine@gmail.com> writes:
> After not emailing for a while, I have some good news: a JIT engine is working!
Great news!
I have been behind on things a bit, so apologies for taking a month to
get back to you, and then only partially. In any case Ludovic probably
knows more both about assembly and JIT work, so I'm happy to not be a
"gatekeeper" of sorts here...
That said, I am concerned about complexity. The current VM, though
obviously slow, does have the advantage of being relatively
simple. Adding a JIT complicates things. Well, adding another form of
compilation complicates things, JIT or AOT or whatever -- so my primary
concern is that, as we add native compilation, we need to keep things
mentally tractable.
I have worked with many people who seem to be able to keep an inhuman
number of names and relationships in their head at one time. I fear I am
not such a person, so we will have to keep things extra-simple :)
So what I would really like to see would be:
* Ideally, a 4-word objcode representation that includes native
code.
* A well-defined convention for that native code. That's to say that
the native code could come from JIT compilation, or from AOT
compilation.
* A uniform way to invoke native code from the VM, and VM code from
native code -- *preserving tail calls*. This seems to require either
trampolines within the VM or platform-specific tail-call assembly.
* A uniform interface to create JIT code as needed, in the call
instructions. i.e.
if (SCM_UNLIKELY (SCM_NEEDS_JIT (proc)))
scm_jit_x (proc);
or something.
We should be able to merge all of that into Guile before any JIT code
goes in, and maybe even before 2.0 if the patches were small enough and
came fast enough ;-) Then we could take our time experimenting on how
best to do native compilation.
So, to reiterate: *simple*, with a good *tail call* story. If we can
find a solution that has those characteristics, fantastic :)
Andy
--
http://wingolog.org/
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-20 13:37 ` Andy Wingo
@ 2010-11-28 20:56 ` Noah Lavine
2010-11-29 21:25 ` Andy Wingo
2010-11-28 20:58 ` Noah Lavine
1 sibling, 1 reply; 15+ messages in thread
From: Noah Lavine @ 2010-11-28 20:56 UTC (permalink / raw)
To: Andy Wingo; +Cc: guile-devel
Hi,
> I am concerned about complexity.
I agree that complexity is a problem. I just sent an email about
Jitgen, which is something I cooked up to reduce code duplication.
However, I can't tell if it's going to end up reducing code complexity
or increasing it. What do you think?
> So what I would really like to see would be:
>
> * Ideally, a 4-word objcode representation that includes native
> code.
> * A well-defined convention for that native code. That's to say that
> the native code could come from JIT compilation, or from AOT
> compilation.
Let me answer these together, because I think they affect each other.
I've been poking around in the code, and noticed that procs.c has a
reference to "applicable structs". As I understand it, these are
structures that can also act as procedures. Procedures with setters
are implemented this way. Is it possible to use these as containers
for JITed code, and leave the objcode format alone? (This will depend
on me learning how they work, since I don't see documentation for them
right now.)
I've been thinking about this because even if I could put JIT code in
the objcode struct now, that wouldn't make much sense for AOT compiled
code that wouldn't necessarily have or want objcode. It might be
better to pick an interface to compiled code now that would work with
AOT compiled code in the future, as you say.
> * A uniform way to invoke native code from the VM, and VM code from
> native code -- *preserving tail calls*. This seems to require either
> trampolines within the VM or platform-specific tail-call assembly.
This one could be hard. I can make JITed code call the VM as a tail
call, because libjit will generate tail calls if you ask it to, but I
don't see how to get from C code to JIT code without pushing onto the
stack without either some assembly code or a trampoline. I think a
trampoline would be easier, but I will ask on the libjit mailing list
how people have solved this before.
> * A uniform interface to create JIT code as needed, in the call
> instructions. i.e.
> if (SCM_UNLIKELY (SCM_NEEDS_JIT (proc)))
> scm_jit_x (proc);
> or something.
That seems easy enough.
Noah
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-20 13:37 ` Andy Wingo
2010-11-28 20:56 ` Noah Lavine
@ 2010-11-28 20:58 ` Noah Lavine
2010-11-28 22:36 ` Ludovic Courtès
1 sibling, 1 reply; 15+ messages in thread
From: Noah Lavine @ 2010-11-28 20:58 UTC (permalink / raw)
To: Andy Wingo; +Cc: guile-devel
Hi,
> I am concerned about complexity.
I agree that complexity is a problem. I just sent an email about
Jitgen, which is something I cooked up to reduce code duplication.
However, I can't tell if it's going to end up reducing code complexity
or increasing it. What do you think?
> So what I would really like to see would be:
>
> * Ideally, a 4-word objcode representation that includes native
> code.
> * A well-defined convention for that native code. That's to say that
> the native code could come from JIT compilation, or from AOT
> compilation.
Let me answer these together, because I think they affect each other.
I've been poking around in the code, and noticed that procs.c has a
reference to "applicable structs". As I understand it, these are
structures that can also act as procedures. Procedures with setters
are implemented this way. Is it possible to use these as containers
for JITed code, and leave the objcode format alone? (And on a related
note, is there any documentation for them?)
I've been thinking about this because even if I could put JIT code in
the objcode struct now, that wouldn't make much sense for AOT compiled
code that wouldn't necessarily have or want objcode. It might be
better to pick an interface to compiled code now that would work with
AOT compiled code in the future, as you say.
> * A uniform way to invoke native code from the VM, and VM code from
> native code -- *preserving tail calls*. This seems to require either
> trampolines within the VM or platform-specific tail-call assembly.
This one could be hard. I can make JITed code call the VM as a tail
call, because libjit will generate tail calls if you ask it to, but I
don't see how to get from C code to JIT code without pushing onto the
stack without either some assembly code or a trampoline. I think a
trampoline would be easier, but I will ask on the libjit mailing list
how people have solved this before.
> * A uniform interface to create JIT code as needed, in the call
> instructions. i.e.
> if (SCM_UNLIKELY (SCM_NEEDS_JIT (proc)))
> scm_jit_x (proc);
> or something.
That seems easy enough.
Noah
On Sat, Nov 20, 2010 at 8:37 AM, Andy Wingo <wingo@pobox.com> wrote:
> Hi,
>
> On Fri 22 Oct 2010 06:29, Noah Lavine <noah.b.lavine@gmail.com> writes:
>
>> After not emailing for a while, I have some good news: a JIT engine is working!
>
> Great news!
>
> I have been behind on things a bit, so apologies for taking a month to
> get back to you, and then only partially. In any case Ludovic probably
> knows more both about assembly and JIT work, so I'm happy to not be a
> "gatekeeper" of sorts here...
>
> That said, I am concerned about complexity. The current VM, though
> obviously slow, does have the advantage of being relatively
> simple. Adding a JIT complicates things. Well, adding another form of
> compilation complicates things, JIT or AOT or whatever -- so my primary
> concern is that, as we add native compilation, we need to keep things
> mentally tractable.
>
> I have worked with many people who seem to be able to keep an inhuman
> number of names and relationships in their head at one time. I fear I am
> not such a person, so we will have to keep things extra-simple :)
>
> So what I would really like to see would be:
>
> * Ideally, a 4-word objcode representation that includes native
> code.
>
> * A well-defined convention for that native code. That's to say that
> the native code could come from JIT compilation, or from AOT
> compilation.
>
> * A uniform way to invoke native code from the VM, and VM code from
> native code -- *preserving tail calls*. This seems to require either
> trampolines within the VM or platform-specific tail-call assembly.
>
> * A uniform interface to create JIT code as needed, in the call
> instructions. i.e.
> if (SCM_UNLIKELY (SCM_NEEDS_JIT (proc)))
> scm_jit_x (proc);
> or something.
>
> We should be able to merge all of that into Guile before any JIT code
> goes in, and maybe even before 2.0 if the patches were small enough and
> came fast enough ;-) Then we could take our time experimenting on how
> best to do native compilation.
>
> So, to reiterate: *simple*, with a good *tail call* story. If we can
> find a solution that has those characteristics, fantastic :)
>
> Andy
> --
> http://wingolog.org/
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-28 20:58 ` Noah Lavine
@ 2010-11-28 22:36 ` Ludovic Courtès
2010-11-29 7:18 ` Ken Raeburn
0 siblings, 1 reply; 15+ messages in thread
From: Ludovic Courtès @ 2010-11-28 22:36 UTC (permalink / raw)
To: guile-devel
Hi!
>> * A uniform way to invoke native code from the VM, and VM code from
>> native code -- *preserving tail calls*. This seems to require either
>> trampolines within the VM or platform-specific tail-call assembly.
>
> This one could be hard. I can make JITed code call the VM as a tail
> call, because libjit will generate tail calls if you ask it to, but I
> don't see how to get from C code to JIT code without pushing onto the
> stack without either some assembly code or a trampoline.
You could cheat and assume that the C compiler does TCO (GCC 4.x does).
Thanks,
Ludo’.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-28 22:36 ` Ludovic Courtès
@ 2010-11-29 7:18 ` Ken Raeburn
0 siblings, 0 replies; 15+ messages in thread
From: Ken Raeburn @ 2010-11-29 7:18 UTC (permalink / raw)
To: Ludovic Courtès; +Cc: guile-devel
On Nov 28, 2010, at 17:36, Ludovic Courtès wrote:
>>> * A uniform way to invoke native code from the VM, and VM code from
>>> native code -- *preserving tail calls*. This seems to require either
>>> trampolines within the VM or platform-specific tail-call assembly.
>>
>> This one could be hard. I can make JITed code call the VM as a tail
>> call, because libjit will generate tail calls if you ask it to, but I
>> don't see how to get from C code to JIT code without pushing onto the
>> stack without either some assembly code or a trampoline.
>
> You could cheat and assume that the C compiler does TCO (GCC 4.x does).
Last I checked, this was dependent on the platform, calling sequence style, stack layout, numbers of arguments, etc. So it wouldn't always happen. And not if optimization were turned off (for better debugging), or certain options given to the compiler, etc. Then there's the whole question of supporting non-GCC compilers.
Seems like a bad assumption to me....
Ken
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-28 20:56 ` Noah Lavine
@ 2010-11-29 21:25 ` Andy Wingo
2010-12-02 3:58 ` Noah Lavine
0 siblings, 1 reply; 15+ messages in thread
From: Andy Wingo @ 2010-11-29 21:25 UTC (permalink / raw)
To: Noah Lavine; +Cc: guile-devel
On Sun 28 Nov 2010 21:56, Noah Lavine <noah.b.lavine@gmail.com> writes:
> I've been poking around in the code, and noticed that procs.c has a
> reference to "applicable structs".
They aren't as efficient as they could be. Currently applicable structs
are the only nonuniform case in procedure calls -- I was trying to get
procedure calls to have no conditional branches, and they (and
applicable smobs) are the only odd cases.
I prefer the trampoline approach used by primitives, continuations,
foreign functions, etc -- they are normal procedures, whose code is a
stub that does the type-specific dispatch.
This is also (and even more the case) what you want for native
procedures -- a native procedure call should just be a jmp and arg
shuffle. Objects which don't have native implementations should have a
stub that does the right thing.
> I don't see how to get from C code to JIT code without pushing onto
> the stack without either some assembly code or a trampoline.
Well, that's what a jit library is for, no? :) Presumably it knows the
calling convention for C, so it should know how to do a tail call from C
-- implemented in assembly of course.
Cheers,
Andy
--
http://wingolog.org/
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-11-29 21:25 ` Andy Wingo
@ 2010-12-02 3:58 ` Noah Lavine
2010-12-06 22:06 ` Andy Wingo
0 siblings, 1 reply; 15+ messages in thread
From: Noah Lavine @ 2010-12-02 3:58 UTC (permalink / raw)
To: Andy Wingo; +Cc: guile-devel
Hello,
I need to apologize for some temporary insanity when I wrote that last
post. I do know a way to get JITed code working correctly with tail
calls. It's not quite as efficient as I would like, but it's decent.
We have the JITed code be a C function that returns an enum to
indicate what the VM should do next. One value means "return
normally", one means "signal error", one means "tail call". For tail
calls, the JIT-code function returns before the tail call is made, so
the stack doesn't get bigger.
About tail calls - it would be better to not even make that call, but
any way to do that that I can see would require non-portable compiler
extensions (probably inline assembler). As you say, the JIT library
knows how to make a tail call, but unfortunately it will only return
code in the form of a C function, and calling that function would push
a frame onto the stack. We could try to do tail calls with a goto, but
labels as values are a GCC extension.
I looked around a bit for a library of inline assembler that would let
us do tail calls on different C compilers and architectures, but I
didn't find one. So I suggest that I first make the JIT work in the
non-ideal way and then start work on that library :-). (I am not at
all attached to my hack if someone knows a better way, though.)
>> I've been poking around in the code, and noticed that procs.c has a
>> reference to "applicable structs".
>
> They aren't as efficient as they could be. Currently applicable structs
> are the only nonuniform case in procedure calls -- I was trying to get
> procedure calls to have no conditional branches, and they (and
> applicable smobs) are the only odd cases.
>
> I prefer the trampoline approach used by primitives, continuations,
> foreign functions, etc -- they are normal procedures, whose code is a
> stub that does the type-specific dispatch.
>
> This is also (and even more the case) what you want for native
> procedures -- a native procedure call should just be a jmp and arg
> shuffle. Objects which don't have native implementations should have a
> stub that does the right thing.
After looking at continuations and foreign functions, it looks like
they generate objcode that calls a special VM instruction which
actually implements what they do. When I think about this, I get to a
pretty weird inversion of how procedure calls currently work. I think
it's what you meant, but let me give my reasoning to be sure:
The trouble with doing it exactly like continuations and foreign
functions is that every procedure could potentially be JITed, so every
function would have to have the special VM instruction. So that leaves
three possibilities:
1. Make the regular 'call' instruction be the one that implements
JITing. But this would introduce a branch to procedure calls.
2. Add an instruction, to be put in the normal function preamble, that
decides whether or not to JIT the function that is currently
executing, and a second instruction that calls a JITed function. If
the first instruction decides to JIT, it would have to a) JIT the
current objcode and b) modify the objcode struct in use to contain
stub code that would include the second instruction, so future calls
would call the JITed code.
3. Change calls so that all procedure calls are actually calls to C
functions, presumably changing the struct that procedures live in as
well. Then JITed functions would just have their code there, and
un-JITed functions would have vm_debug_engine (or a wrapper) as their
native function.
It sounds like 2 is what you want. Is that right? (Currently I'm doing
option 1.)
Noah
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-12-02 3:58 ` Noah Lavine
@ 2010-12-06 22:06 ` Andy Wingo
2010-12-06 22:53 ` Noah Lavine
0 siblings, 1 reply; 15+ messages in thread
From: Andy Wingo @ 2010-12-06 22:06 UTC (permalink / raw)
To: Noah Lavine; +Cc: guile-devel
Hi Noah,
Apologies for the stochastic response times here...
On Thu 02 Dec 2010 04:58, Noah Lavine <noah.b.lavine@gmail.com> writes:
> have the JITed code be a C function that returns an enum to indicate
> what the VM should do next.
This is widely known as the "trampoline" approach -- you fall down to
some known dispatcher, then bounce back up. It would be nice to avoid it
though.
And actually, terminology-wise, I should have been more precise when I
said:
>> I prefer the trampoline approach used by primitives, continuations,
>> foreign functions, etc -- they are normal procedures, whose code is a
>> stub that does the type-specific dispatch.
Let's call this sort of thing the "stub" pattern.
> 1. Make the regular 'call' instruction be the one that implements
> JITing. But this would introduce a branch to procedure calls.
Regarding whether or not to jit, I'm OK with doing that in the `call'
instruction.
> 2. Add an instruction, to be put in the normal function preamble, that
> decides whether or not to JIT the function that is currently
> executing
I think this would be too much overhead.
> It sounds like 2 is what you want. Is that right? (Currently I'm doing
> option 1.)
No, I think what you're doing is right in this regard.
But regarding how to call a natively-compiled function -- I think this
is really, really critical. It's an important decision that will have
some far-reaching effects. I would like for natively-compiled functions
to get their objcode replaced with a short stub that tail-calls the
native code -- maybe the stub would consist of just one instruction,
`native-call' or something. (Obviously this instruction doesn't exist
yet). That instruction would do a tail call in such a way that the vm
engine is no longer on the stack.
I am concerned about JIT libraries precisely because they tend to impose
a C calling convention on native code, without allowing for more
appropriate conventions that allow for tail calls and multiple values on
the stack.
Anyway, these are my concerns. Obviously you're the one hacking the
code, so do what you like :)
Happy hacking,
Andy
--
http://wingolog.org/
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: A Working (but Minimal) JIT
2010-12-06 22:06 ` Andy Wingo
@ 2010-12-06 22:53 ` Noah Lavine
0 siblings, 0 replies; 15+ messages in thread
From: Noah Lavine @ 2010-12-06 22:53 UTC (permalink / raw)
To: Andy Wingo; +Cc: guile-devel
Hi,
> Regarding whether or not to jit, I'm OK with doing that in the `call'
> instruction.
Sounds good. I'll put it in there. (And also in 'mv-call' and 'tail-call'.)
> But regarding how to call a natively-compiled function -- I think this
> is really, really critical. It's an important decision that will have
> some far-reaching effects. I would like for natively-compiled functions
> to get their objcode replaced with a short stub that tail-calls the
> native code -- maybe the stub would consist of just one instruction,
> `native-call' or something. (Obviously this instruction doesn't exist
> yet). That instruction would do a tail call in such a way that the vm
> engine is no longer on the stack.
>
> I am concerned about JIT libraries precisely because they tend to impose
> a C calling convention on native code, without allowing for more
> appropriate conventions that allow for tail calls and multiple values on
> the stack.
I see what you mean, but I don't know of a library that fixes that.
The problem I'm having with the current code is that (I think) there
is no portable way to jump from C code to another function (or block
of machine code of any sort) without pushing a frame on the stack. A
normal C function call makes the stack deeper (unless the compiler
optimizes it, which is a GCC thing), and using a goto where the target
isn't fixed at compile time is a GCC extension. And the only other way
for a C program to do a jump is setjmp and longjmp, but I don't see a
way to use those for this.
I think that the best way is to use inline assembly to make the tail
call. But in order to do that portably, we would need a macro that
expanded to different code for GCC, MSVC, and any other compilers that
Guile wanted to support, as well as different code for different
architectures. My idea is to first do it with a trampoline, and then
maybe replace the trampoline with a macro like that.
How does all of this sound?
> Anyway, these are my concerns. Obviously you're the one hacking the
> code, so do what you like :)
I agree with your concerns. Thanks a lot for showing me how to do this.
Noah
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2010-12-06 22:53 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-10-22 4:29 A Working (but Minimal) JIT Noah Lavine
2010-10-22 21:46 ` Phil
2010-10-27 21:17 ` Ludovic Courtès
2010-10-27 21:10 ` Ludovic Courtès
2010-10-27 22:53 ` Noah Lavine
2010-11-02 22:51 ` Ludovic Courtès
2010-11-20 13:37 ` Andy Wingo
2010-11-28 20:56 ` Noah Lavine
2010-11-29 21:25 ` Andy Wingo
2010-12-02 3:58 ` Noah Lavine
2010-12-06 22:06 ` Andy Wingo
2010-12-06 22:53 ` Noah Lavine
2010-11-28 20:58 ` Noah Lavine
2010-11-28 22:36 ` Ludovic Courtès
2010-11-29 7:18 ` Ken Raeburn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).