Alien-LibJIT
view release on metacpan or search on metacpan
libjit/jit/jit-memory-cache.c view on Meta::CPAN
JIT_CACHE_OK The function call was successful.
JIT_CACHE_RESTART The cache does not currently have enough
space to fit any method. This code may
only be returned if the "factor" value
was 0. In this case it is necessary to
restart the method output process by
calling _jit_cache_start_method again
with a bigger "factor" value.
JIT_CACHE_TOO_BIG The cache does not have any space left
for allocation. In this case a restart
won't help.
Some CPU optimization guides recommend that labels should be aligned.
This can be achieved using _jit_cache_align.
Once the method code has been output, call _jit_cache_end_method to finalize
the process. This function returns one of two result codes:
JIT_CACHE_OK The method output process was successful.
JIT_CACHE_RESTART The cache space overflowed. It is necessary
to restart the method output process by
calling _jit_cache_start_method again
with a bigger "factor" value.
The caller should repeatedly translate the method while _jit_cache_end_method
continues to return JIT_CACHE_END_RESTART. Normally there will be no
more than a single request to restart, but the caller should not rely
upon this. The cache algorithm guarantees that the restart loop will
eventually terminate.
Cache data structure
--------------------
The cache consists of one or more "cache pages", which contain method
code and auxiliary data. The default size for a cache page is 64k
(JIT_CACHE_PAGE_SIZE). The size is adjusted to be a multiple
of the system page size (usually 4k), and then stored in "pageSize".
Method code is written into a cache page starting at the bottom of the
page, and growing upwards. Auxiliary data is written into a cache page
starting at the top of the page, and growing downwards. When the two
regions meet, a new cache page is allocated and the process restarts.
To allow methods bigger than a single cache page it is possible to
allocate a block of consecutive pages as a single unit. The method
code and auxiliary data is written to such a multiple-page block in
the same manner as into an ordinary page.
Each method has one or more jit_cache_method auxiliary data blocks associated
with it. These blocks indicate the start and end of regions within the
method. Normally these regions correspond to exception "try" blocks, or
regular code between "try" blocks.
The jit_cache_method blocks are organised into a red-black tree, which
is used to perform fast lookups by address (_jit_cache_get_method). These
lookups are used when walking the stack during exceptions or security
processing.
Each method can also have offset information associated with it, to map
between native code addresses and offsets within the original bytecode.
This is typically used to support debugging. Offset information is stored
as auxiliary data, attached to the jit_cache_method block.
Threading issues
----------------
Writing a method to the cache, querying a method by address, or querying
offset information for a method, are not thread-safe. The caller should
arrange for a cache lock to be acquired prior to performing these
operations.
Executing methods from the cache is thread-safe, as the method code is
fixed in place once it has been written.
Note: some CPU's require that a special cache flush instruction be
performed before executing method code that has just been written.
This is especially important in SMP environments. It is the caller's
responsibility to perform this flush operation.
We do not provide locking or CPU flush capabilities in the cache
implementation itself, because the caller may need to perform other
duties before flushing the CPU cache or releasing the lock.
The following is the recommended way to map an "jit_function_t" pointer
to a starting address for execution:
Look in "jit_function_t" to see if we already have a starting address.
If so, then bail out.
Acquire the cache lock.
Check again to see if we already have a starting address, just
in case another thread got here first. If so, then release
the cache lock and bail out.
Translate the method.
Update the "jit_function_t" structure to contain the starting address.
Force a CPU cache line flush.
Release the cache lock.
Why aren't methods flushed when the cache fills up?
---------------------------------------------------
In this cache implementation, methods are never "flushed" when the
cache becomes full. Instead, all translation stops. This is not a bug.
It is a feature.
In a multi-threaded environment, it is impossible to know if some
other thread is executing the code of a method that may be a candidate
for flushing. Impossible that is unless one introduces a huge number
of read-write locks, one per method, to prevent a method from being
flushed. The read locks must be acquired on entry to a method, and
released on exit. The write locks are acquired prior to translation.
The overhead of introducing all of these locks and the associated cache
data structures is very high. The only safe thing to do is to assume
that once a method has been translated, its code must be fixed in place
for all time.
We've looked at the code for other Free Software and Open Source JIT's,
and they all use a constantly-growing method cache. No one has found
a solution to this problem, it seems. Suggestions are welcome.
To prevent the cache from chewing up all of system memory, it is possible
( run in 0.552 second using v1.01-cache-2.11-cpan-02777c243ea )