Memory clean for applications using LLVM for JIT compilation

I derived a class from JITMemoryManager which delegates everything to
an instance made with CreateDefaultMemManager(). ExecutionEngine
destroys the wrapper, but I keep the inner instance which did the
actual work. Works, but seems a bit ugly. Did you find any other
solutions?

I ended up implementing the exact same thing, it feels dirty but it has worked great for us so far.

Thanks for the reply, nice to have some validation. I thought of
another approach which might be preferable:
generate relocatable code, use a JITEventListener to grab each
function and copy it to my own memory,
let all the LLVM stuff die normally then use my copy of the code.
However when I call setRelocationModel(Reloc::PIC_) on the engine
builder I get code that seg faults.
The assembly looks plausible at a glance, but I'm not really up to
speed on x86 assembly.
Is PIC supposed to work with JIT on X86-32?

I'm not sure I see why the delegating memory manager feels wrong to both of you. That's exactly the kind of usage model I would envision for clients that needed to handle multiple ExecutionEngines that had reason to share a memory manager. I'm saying this as an invitation to discussion, not to be argumentative. We certainly could change things if it would make the design better.

Basically, the intention of the current design is to keep the ExecutionEngine ignorant of memory allocation details. I'm not sure it always works that way, but that's the intent. I believe that the decision to have the ExecutionEngine own the memory manager was made so that it would be self-contained and capable of cleaning up after itself.

The JIT engine has a rather ad hoc history and is more complicated than anyone would want. It allocates and links together more things than it directly exposes and the lifecycles of different bits of memory aren't always obvious, especially if you get into re-JITing code. I'm not sure there's a lot that can be done in the general case to keep it clean other than to have the engine own everything.

I don't believe that the JIT engine is designed to handle having its function memory moved, so even if you could work out the relocation issues I don't think it's a good idea.

The MCJIT engine, on the other hand, is designed to have its memory moved on the fly (though at the granularity of sections, not functions). The MCJIT engine does not work with the PIC relocation model on 32-bit x86 targets, but it will let you reapply relocations with other models after you move things. There's some code in the lli utility that does this if you're interested.

I'm not sure what the current maintenance model is for the older JIT engine. Near as I can tell when it grows at all it is driven by people who have a need making specific contributions and as such it doesn't have a particularly coherent direction.

I have more idealistic hopes for the MCJIT engine, and if you're interested in using it I'd be happy to talk to you about future design directions.

-Andy

It seems wrong to delegate a dozen or more methods when I only want to
change the behavior in a couple small ways. It would have been easier
to derive a class, but the base class is inaccessible (in an anonymous
namespace). And I was afraid to roll my own memory manager because I
didn't understand what all those methods do.
So MCJIT is future - will JITMemoryManager remain relevant? If so I
can send a patch to make it possible to derive from the default
implementation.
In my case (compiling shaders in mesa, a graphics library) an engine
is used once then thrown away so I don't think there's a risk of
confusing it by moving the code.
Thanks for the tip about relocating with MCJIT.

Hi Frank,

I'm not sure I completely understand what your delegating memory manager is doing, but I probably don't need to. If your primary objection is that you can't derive from the DefaultJITMemoryManager then I personally wouldn't object to your submitting a patch to make that accessible as a base class.

The future path for JIT and MCJIT is under debate. A few of us would really like to see MCJIT replace JIT entirely, but it can't do that until it meets all of the needs of the people currently using the JIT (which it doesn't yet). I think we'll get there, but I don't have a clear timeline.

Currently the memory manager interfaces for JIT and MCJIT are overlaid on top of one another and have minimal overlap. I'd like to separate them completely in the near future. As long as the old JIT engine is still around the old JITMemoryManager (and its default implementation) will still be relevant, but neither one is particularly relevant to MCJIT even now.

In case you haven't been following the discussions, the primary shortcomings of the MCJIT as compared to the older JIT are that it doesn't allow lazy compilation at the function level, it has limited support for multiple modules and you can't recompile/relink functions. There are some explorations in progress to figure out how to deal with these limitations, but nothing is imminent.

If these limitations aren't a concern for you, I suggest you give MCJIT a try. It has some nice benefits and is being more actively maintained.

-Andy

"Kaylor, Andrew" <andrew.kaylor@intel.com> writes:

In case you haven't been following the discussions, the primary
shortcomings of the MCJIT as compared to the older JIT are that it
doesn't allow lazy compilation at the function level, it has limited
support for multiple modules and you can't recompile/relink functions.

Another limitation is that MCJIT imposes the resctrictions associated to
the object file model being used to its JIT users. Symbol naming, for
instance. I don't know if there are others.

Hi Andy,

One of the issues that I found not intuitive, is that when an ExecutionEngine is deallocated, the memory manager's destructor is also called. This resulted in having to write two objects in my case, one as a per JIT request memory manager and one global JIT memory manager.

The per request JIT memory manager gets memory from the global manager, but both ended up implementing (almost) the whole memory manager api, because I couldn't find another way to do it. Also see the code here:

https://github.com/rubinius/rubinius/blob/master/vm/llvm/jit_memory_manager.hpp
https://github.com/rubinius/rubinius/blob/master/vm/llvm/jit_memory_manager.cpp

If there are better ways to clean up the code, that would be great, but having to things like that is what made me state feeling dirty on how it was implemented.

Hi Dirkjan,

Are you using JIT or MCJIT?

Cheers.

Hi Manny,

We're using JIT atm, but probably switch to MCJIT at some point, haven't investigated it much though.