[LLVMdev] Redefining function

Jeffrey Yasskin jyasskin at google.com
Sat Jan 30 21:25:05 PST 2010


On Sat, Jan 30, 2010 at 6:22 PM, Conrado Miranda
<miranda.conrado at gmail.com> wrote:
> Albert Graef wrote:
>>
>> The way I do this in Pure is to always call global functions in an
>> indirect fashion, using an internal global variable which holds the
>> current function pointer. When a function definition gets updated, the
>> Pure interpreter just jits the new function, changes the global variable
>> accordingly, and frees the old code.
>>
>> Compared to Duncan's suggestion, this has the advantage that you only
>> have to recompile the function which was changed. AFAICT, if you use
>> replaceAllUsesWith, then the changes ripple through so that you might
>> end up re-jiting most of your program.
>
> Thought of that before, but I was trying to do it more elegantly and
> transparent to the program (which is being write in C/C++). Maybe going back
> to that.
>
> Thank you both for the quick replies.
>
> Miranda
>
> PS:
> If it's any help, got the svn version and, while running the program, got
> this:
> The JIT doesn't know how to handle a RAUW on a value it has emitted.
> UNREACHABLE executed at
> /home/conrado/engines/llvm/lib/ExecutionEngine/JIT/JITEmitter.cpp:1542!
>
> I looked at the function and it's a dummy function. Just looking forward to
> see that corrected.

The problem here is reasonably complicated. With the JIT, you have two
different worlds that aren't automatically in sync: the IR in your
program, and the machine code generated for one version of that IR.

runFunction(F) is a wrapper around getPointerToFunction(F), which
returns the address of some machine code implementing the function.
runFunction() does _not_ free this machine code when it returns, so
subsequent runFunction() calls don't need to re-compile it, but they
also get the original definition even if the function has changed. The
JIT will automatically destroy the machine code when F is destroyed,
or you can destroy it manually with freeMachineCodeForFunction().

If you have an existing IR function A which calls function B, and
you've emitted A to machine code, then you have a machine code call to
B in there. Now you want A to call C instead. Without the above
assert, it would be relatively easy to change the IR to call C: call
B->replaceAllUsesWith(C). However, you still have the machine code for
A, which calls B, and there could be a thread concurrently executing
A, making it unsafe to modify A's code. So what should the JIT do when
it sees you replacing B with C?
 1. It could do nothing. Then it would be your responsibility to wait
for all threads to finish running A, free its machine code, and then
recompile it with the new call. (You can do the recompile without
freeing the old code by calling
ExecutionEngine::recompileAndRelinkFunction(A), but that'll
permanently leak the old code.) If you destroy B while a thread is
still in A, its machine code gets freed, leaving you with a latent
crash.
 2. It could compile C, and either replace B's machine code with a
jump to C, or replace all calls to B with calls to C. Aside from not
having the infrastructure to do this, it's not thread-safe:
http://llvm.org/PR5184.
 3. ???

You'd have an extra option if machine code lifetimes weren't tied to
llvm::Function lifetimes, but I haven't spent the time to get that
working.

Since I didn't have a use for RAUW on a compiled function, I resisted
the temptation to guess at the right behavior and put in that assert.
If you think you know what the right behavior is, feel free to file a
bug asking for it.

You can work around this by using freeMachineCodeForFunction yourself
on the whole call tree, then using RAUW to replace the functions, and
then re-compiling them.

Or you can take Albert's advice to make all calls through function
pointers. This will be a bit slower, but should Just Work.



More information about the llvm-dev mailing list