[LLVMdev] (Very) small patch for the jit event listener

Gaël Thomas gael.thomas at lip6.fr
Wed Nov 13 17:23:55 PST 2013


Hi Andy,

Thanks for the answer. I'm currently reading the internal code of
MCJIT and it's really a great work (I was only using the
ExecutionEngine interface for the moment). So, I agree, all what I
need is already in the code (see below) :)

2013/11/14 Kaylor, Andrew <andrew.kaylor at intel.com>:
> Hi Gaël,
>
> Thank you for the detailed explanation.  It's very helpful.
>
> All of the things you describe could be done within MCJIT, but I'm not sure that's where they belong.  We had a discussion about lazy function compilation at the LLVM Developers Meeting last week and the consensus among those present was that it would be better to leave this sort of lazy compilation to the MCJIT client rather than having MCJIT try to solve the problem because each client knows more about how it should be implemented for their particular case than MCJIT can possibly know.  We could, perhaps, provide a reference implementation (outside of MCJIT) but it would likely be a very simple solution not well-suited for use in real-world programs.

I understand the point. Probably that providing a small example that
describes how using advanced features of MCJIT could help. If I can
manage to make MCJIT works with VMKit, I'll be happy to send you an
example of lazy compilation that highlight some of the features of
MCJIT.

>
> The approach you describe with home-made stubs seems good.  Now we just need to figure out why it isn't working!
>
> To begin with, there problem is an issue with naming.  In order to get the home-made stub solution to work, you'll need to distinguish between the name of the function as it appears at the call site (which will result in a call to your stub function) and the name of the function as it is defined in the implementation module.

I don't know if I understand, you say that having two different names
is required, or simply that it's more easy to manage? Because I can
have to generate a lot of calls to g before compiling it and it means
that I will have to generate a lot of different names (not a big deal,
but it takes time during the execution)...

> For instance, you'll probably want the module for 'f' to look something like this:
>
> ----
>
> declare i32 @g_stub()
>
> define i32 @f() {
>   %r = call i32 @g_stub( )
>   ret i32 %r
> }
>
> ----
>
> and the module for 'g' will look something like this:
>
> ----
>
> define i32 @g() {
>   ret i32 0
> }
>
> ----
>
> Now when you generate code for the 'f' module, MCJIT will call the memory manager asking for the address of 'g_stub' and you'll give it a pointer to your stub function.  When your stub function gets called, you'll call MCJIT::getFunctionAddress('g') and MCJIT will generate code for the 'g' module and return the address of the real 'g' function.
>
> You'll need to keep the stub around as a pass-through because the address of the stub is now baked into the code that was generated for 'f',

But the location of the call-site (the pointer to g_stub in the
generated code) should be in the relocation table of the module f? You
don't think that a client could reuse this relocation table directly?
(as we can do to dynamically modify a function pointer in a shared
library)

> but that's a good thing is you might eventually want to replace the function because it gives you a single point to redirect calls to 'g' from any module that is calling it through 'g_stub'.

For performance, it's not perfect (two calls to reach a function), but
I agree, it simplifies the management.

> If it's really important to you to be able to modify the 'f' call site to go directly to 'g' once its generated, you may be able to do that with the patchpoint stuff, but I don't know the details.

Yes, I'm pretty sure that with patchpoint, I can handle this problem.

>
> Anyway, except for the stub naming, that sounds an awful lot like what you described.  If using the names as I describe doesn't fix things for you, we may have a bug to fix.

cool :) I will try this week-end and I will tell you if it works.

>
> The inlining problem is perhaps a bit trickier.  What I would suggest in that case is that you basically need to link your modules against the library module that you want to use for inlining before you pass the modules to MCJIT.  There's an Intel product that does this, so I know it can be made to work.  I'm not sure there's a simple interface for it.  Basically what you need to do is extract the functions from your runtime library into the module you want to optimize and then run it through whatever optimization passes you're interested in.
>  I'm sure there are some helper classes that could be implemented to make this easier, but the details are ultimately implementation specific.  This is a problem with the old JIT too though, right?
>
> Inlining the code of an already compiled module is an even tougher problem.  That's really something that would need to be addressed at the code generation level, I think.  If you had the generated code in a sufficiently general form you could probably do this with an intrinsic (assuming you definitely knew you wanted to inline it).  Generally I think it's outside the design space that MCJIT intends to target.

Yes, I also think so, it's a problem of code generation. I will try to
adapt the current SimpleInline pass of llvm and use the internal state
of VMKit/J3 to retrieve the IR of an already generated function. As I
have to keep a map that associates names to llvm::Function in VMKit, I
just have to use it to find the code.

>
> So, to summarize, I think that the current implementation of MCJIT is sufficient to address your lazy compilation needs, though I'd be happy to continue the discussion if you think something more is needed.

Yes, after your answer and after having taken a look inside the MCJIT
code, I'm pretty sure that I can use MCJIT for lazy compilation.

> That leaves me with your "safepoint" issue, which I don't have a clear picture of.

I'm not at this point for the moment :) As soon as I'm able to make
VMKit runs with MCJIT (without the garbage collector), I will try to
find exactly what is missing for the GC (and I will also explore the
safepoint/patchpoint patch to see if I can use it in VMKit for the GC,
but it's not related to this topic).

Thank you for everything, I'll tell you soon if it works,

Gaël

>
> -Andy
>
>
> -----Original Message-----
> From: Gaël Thomas [mailto:gael.thomas at lip6.fr]
> Sent: Wednesday, November 13, 2013 2:46 PM
> To: Yaron Keren
> Cc: Kaylor, Andrew; llvm-commits at cs.uiuc.edu; LLVM Dev
> Subject: Re: (Very) small patch for the jit event listener
>
> Hi Andrew, hi all,
>
> I already saw that the old jit was (almost) deprecated. So, I'm currently playing with the new jit and it's look very interesting.
> (I'm working locally and I haven't pushed anything new on VMKit because I'm also changing a little the design vmkit). For the moment, MCJIT does not work with VMKit (but I haven't yet tested the safepoint/stackmap patch), I don't know if it comes from what I'm doing or if something is still missing in MCJIT.
>
> Sorry, my mail will be too long, but I want first to explain what I'm currently doing and then open the discussion to explain what I would like to see in MCJIT :) Of course, I can help to develop something or to give feed back, I'm hardly working on vmkit currently and I have time to spend on that (but I'm far from an expert in compilation stuffs!).
>
> Basically, I want to compile lazily my functions (we have a Java virtual machine built upon vmkit and compiling all the rt.jar during the bootstrap is not very realistic:)). So, by lazy compilation, I mean that I want to compile (and even resolve) a function only when it is used. For example, during the compilation of void f() { g() } I don't want to compile g(). I will only compile g() when it will be called.
>
> For the moment, I use a home-made stub (I have attached the asm code, it can give you some ideas if you plan to integrate a stub generator able to perform dynamic dispatch like virtual call in c++) because MCJIT does not provide this facility. So, for each function, I define a module. In each module, I have to define the runtime function (such as gcmalloc, throwException and this kind of functions). They are defined in a separated runtime module populated during the bootstrap.
> I have thus this picture
>
> MCJIT
>   |----------------------------------------------------------
>   |                                    |                        |
> Runtime module     module for f   module for g
>
> When I see that I need g during the compilation of f, I define an external symbol g in f's module and I add a global mapping between g and its stub in MCJIT. Everything works perfectly in the old JIT, so the code is correct in this case. The problem that I face with multiple modules in the old JIT is that the symbol g defined in f's module and the symbol g defined in g's module are not the same, I thus have to define multiple symbol in the old JIT for the same entity, which is far from perfect. Anyway, it works.
>
> With MCJIT, I can call f from my C++ code (relatively easy as f is defined in its own module),  the stub of g is called and I can generate its code (which use other functions). But I can not find a way to compile g and to update the mapping between g and the new function pointer. When I use recompileAndRelinkFunction, I see that it is not implemented in MCJIT, and when I use getFunctionPointer, I obtain.... a null pointer? I have not investigated further, but probably, having two symbols g in the same MCJIT does not work. And I don't see what I can do at this step? Maybe that I have missed something?
>
> Otherwise, I already predict that I will also have one big problem
> latter: I would like to inline functions from the runtime module in f or g, and I would like to inline the code of an already compiled function h in g. So, I would like to inline functions that comes from different modules. It means that I would like to see MCJIT working like an llvm::Linker, able to resolve the h symbol during the compilation of g. And for the moment, as MCJIT can not see that the g defined in f's module and the g defined in g's module represent the same function, I think that I will have a problem latter.
>
> So, for the moment, I'm a little bit stuck with MCJIT. Something that could be really useful could be a mcjit that acts as a linker. If MCJIT could have a map like this (I give a pseudo-c++ code)
>
> class FnDescriptor {
>   StringRef name;
>   FunctionType fnType;
>   LinkageType linkage;
> };
>
> class FnState {
>   llvm::Module* definedModule;
> /* maybe a  llvm::ObjectImage* ? */
>   List<RelocationTable*>* users,
>   void* currentPointer
> };
>
> map<FnDescriptor, FnState>
>
> it could be really useful. Let's imagine the same scenario with f that calls g while g is not yet compiled. At the beginning of this scenario, "g" "void ()" could simply be associated to a FnState with
>
> <null, List<>, stubForG>.
>
> After f's compilation, it could be something like that
>
> <null, List<RelocationTable-of-module-f>, stubForG>.
>
> And after the compilation of g, something like
>
> <moduleOfG, List<Reloc-f, Reloc-g>, compiledCode-of-g>
>
> with the relocation entries updated?
>
> Otherwise, for safepoints, and for exception tables, it could be also really useful to install call backs to let VMKit manages them itself (but it's maybe provided by the safepoint/patchpoint patch?)? (with something that can make the association between a MCSymbol and it's actual address of course :) )
>
> See you!
> Gaël
>
> PS: by the way, Yaron, we currently face almost the same problems
>
>
> 2013/11/13 Yaron Keren <yaron.keren at gmail.com>:
>> Hi Andy,
>>
>> We had previous discussions about this, I'd like to state more exactly
>> what features would make MCJIT a replacement for the JIT.
>> After putting significant effort trying to move to MCJIT, I'm
>> currently back with the JIT. This is in a REPL environment where
>> functions are added and removed dynamically and response time is
>> important. The issue is the legacy JIT provides great flexibility for
>> this use case which is currently missing from MCJIT because of their very different design and goals.
>>
>> With JIT, you can modify Function(s) in an already-compiled Module,
>> unload the machine code and the JIT will automatically recompile and
>> relink the function next time it is called. To make MCJIT work like
>> that it would need at least :
>>
>> 1) Automatic module splitting into function-modules.
>> 2) Module delete: from module list, from linker namespace, machine
>> code unload, unregister EH and debuginfo.
>> 3) Stub functions.
>> 4) Relinking with stub functions so that new modules are relinked
>> without changing already-finalized modules. This is critical to
>> response time as you may change just one function out of 1000.
>> 5) Module addition should register EH and debuginfo (this is not done
>> with current JIT but while at it...).
>>
>> REPL environments using the LLVM JIT would likely encounter great
>> difficulty moving to the current MCJIT without the above. 1) could be
>> done by the programmer but the a helper function should provide this
>> service. 2)-4) could be done only in the MCJIT. 5) is a bonus.
>>
>> Until MCJIT has this kind of flexibility, I hope the JIT would be kept
>> alive.
>>
>> Yaron
>>
>>
>>
>>
>>
>>
>> 2013/11/13 Kaylor, Andrew <andrew.kaylor at intel.com>
>>>
>>> Hi Gaël,
>>>
>>> I'm not familiar enough with the details of the old JIT engine and
>>> its event interface to comment on whether or not your changes are
>>> appropriate, but I'm not sure anyone is so the patch is probably OK
>>> as is.  I don't see any obvious problems with it.
>>>
>>> However, your description of the changes raises a bigger issue in my mind.
>>> I'm not sure if you are aware of this, but we're planning to
>>> deprecate the old JIT engine in a future release -- possibly as soon
>>> as LLVM 3.5.  In order to do so we need to make sure the MCJIT engine
>>> is capable of meeting the needs of current JIT users, and I'm not
>>> sure we've got your case fully covered yet.
>>>
>>> Can you tell me a little bit more about the details of how you are
>>> using the JIT engine?  I'm putting together a document describing
>>> various models for MCJIT use and if your model isn't covered by one
>>> of the cases I've got now I'd like to add it.
>>>
>>> Also, have you looked at the recently added Stackmap and Patchpoint
>>> intrinsics.  Without knowing a lot about either your case or those
>>> intrinsics, I think that there may be a possible match there.  The
>>> thing that raised a red flag for me in your message was that MCJIT
>>> doesn't maintain mappings between the generated code and the LLVM
>>> classes from which it is produced, so we'll probably need a different
>>> way to handle your safepoints.
>>>
>>> (BTW, it's probably appropriate to move further discussion to the
>>> LLVMDev list rather than llvm-commits.)
>>>
>>> Thanks,
>>> Andy
>>>
>>>
>>> -----Original Message-----
>>> From: llvm-commits-bounces at cs.uiuc.edu
>>> [mailto:llvm-commits-bounces at cs.uiuc.edu] On Behalf Of Gaël Thomas
>>> Sent: Wednesday, November 13, 2013 6:09 AM
>>> To: llvm-commits at cs.uiuc.edu
>>> Subject: (Very) small patch for the jit event listener
>>>
>>> Hi all,
>>>
>>> We have a small problem for vmkit. We rely on the JITEventListener to
>>> register the safepoints generated for the garbage collector, and for
>>> that purpose, we have to use the JITCodeEmitter (the
>>> MachineCodeEmitter) that was used to generate the MachineFunction in
>>> order to find the physical address of the safepoints (aka, the
>>> MCSymbols). A long time ago, it was not a problem as the JIT class
>>> was in the llvm interface, but today, the header is hidden inside the
>>> lib directory and not installed by llvm. Currently, we directly use
>>> this header, but it means that during the compilation of vmkit, we
>>> need the sources of llvm. But, as we are currently developing a
>>> debian package of vmkit, we would like to avoid the installation of the llvm sources to compile vmkit.
>>>
>>> So, I made a small patch that just adds a new MachineCodeEmitter
>>> field in JITEvent_EmittedFunctionDetails and fill it in
>>> JITCodeEmitter. As the patch only adds a new field in the
>>> JITEvent_EmittedFunctionDetails, it should not break anything. At
>>> least, my llvm and my vmkit are still running :) (by the way, I had
>>> to execute a make clean before recompiling llvm because I think that
>>> a dependency is missing)
>>>
>>> As it is my first patch, I hope that I have used the llvm coding style...
>>>
>>> See you,
>>> Gaël
>>>
>>>
>>>
>>>
>>>
>>>
>>> --
>>> -------------------------------------------------------------------
>>> Gaël Thomas, Associate Professor, UPMC
>>> http://pagesperso-systeme.lip6.fr/Gael.Thomas/
>>> -------------------------------------------------------------------
>>>
>>> _______________________________________________
>>> llvm-commits mailing list
>>> llvm-commits at cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/mailman/listinfo/llvm-commits
>>
>>
>
>
>
> --
> -------------------------------------------------------------------
> Gaël Thomas, Associate Professor, UPMC
> http://pagesperso-systeme.lip6.fr/Gael.Thomas/
> -------------------------------------------------------------------



-- 
-------------------------------------------------------------------
Gaël Thomas, Associate Professor, UPMC
http://pagesperso-systeme.lip6.fr/Gael.Thomas/
-------------------------------------------------------------------




More information about the llvm-dev mailing list