Alien-LibJIT

 view release on metacpan or  search on metacpan

libjit/doc/libjit.texi  view on Meta::CPAN

but a real program would be expected to handle such errors.

@menu
* Tutorial 1::              Tutorial 1 - mul_add
* Tutorial 2::              Tutorial 2 - gcd
* Tutorial 3::              Tutorial 3 - compiling on-demand
* Tutorial 4::              Tutorial 4 - mul_add, C++ version
* Tutorial 5::              Tutorial 5 - gcd, with tail calls
* Dynamic Pascal::          Dynamic Pascal - A full JIT example
@end menu

@c -----------------------------------------------------------------------

@node Tutorial 1, Tutorial 2, Tutorials, Tutorials
@section Tutorial 1 - mul_add
@cindex mul_add tutorial

In the first tutorial, we will build and compile the following function
(the source code can be found in @code{tutorial/t1.c}):

@example
int mul_add(int x, int y, int z)
@{
    return x * y + z;
@}
@end example

@noindent
To use the JIT, we first include the @code{<jit/jit.h>} file:

@example
#include <jit/jit.h>
@end example

All of the header files are placed into the @code{jit} sub-directory,
to separate them out from regular system headers.  When @code{libjit}
is installed, you will typically find these headers in
@code{/usr/local/include/jit} or @code{/usr/include/jit}, depending upon
how your system is configured.  You should also link with the
@code{-ljit} option.

@noindent
Every program that uses @code{libjit} needs to call @code{jit_context_create}:

@example
jit_context_t context;
...
context = jit_context_create();
@end example

Almost everything that is done with @code{libjit} is done relative
to a context.  In particular, a context holds all of the functions
that you have built and compiled.

You can have multiple contexts at any one time, but normally you will
only need one.  Multiple contexts may be useful if you wish to
run multiple virtual machines side by side in the same process,
without them interfering with each other.

Whenever we are constructing a function, we need to lock down the
context to prevent multiple threads from using the builder at a time:

@example
jit_context_build_start(context);
@end example

The next step is to construct the function object that will represent
our @code{mul_add} function:

@example
jit_function_t function;
...
function = jit_function_create(context, signature);
@end example

The @code{signature} is a @code{jit_type_t} object that describes the
function's parameters and return value.  This tells @code{libjit} how
to generate the proper calling conventions for the function:

@example
jit_type_t params[3];
jit_type_t signature;
...
params[0] = jit_type_int;
params[1] = jit_type_int;
params[2] = jit_type_int;
signature = jit_type_create_signature
    (jit_abi_cdecl, jit_type_int, params, 3, 1);
@end example

This declares a function that takes three parameters of type
@code{int} and returns a result of type @code{int}.  We've requested
that the function use the @code{cdecl} application binary interface (ABI),
which indicates normal C calling conventions.  @xref{Types}, for
more information on signature types.

Now that we have a function object, we need to construct the instructions
in its body.  First, we obtain references to each of the function's
parameter values:

@example
jit_value_t x, y, z;
...
x = jit_value_get_param(function, 0);
y = jit_value_get_param(function, 1);
z = jit_value_get_param(function, 2);
@end example

Values are one of the two cornerstones of the @code{libjit} process.
Values represent parameters, local variables, and intermediate
temporary results.  Once we have the parameters, we compute
the result of @code{x * y + z} as follows:

@example
jit_value_t temp1, temp2;
...
temp1 = jit_insn_mul(function, x, y);
temp2 = jit_insn_add(function, temp1, z);
@end example

This demonstrates the other cornerstone of the @code{libjit} process:
instructions.  Each of these instructions takes two values as arguments
and returns a new temporary value with the result.

Students of compiler design will notice that the above statements look
very suspiciously like the "three address statements" that are described
in compiler textbooks.  And that is indeed what they are internally within
@code{libjit}.

If you don't know what three address statements are, then don't worry.
The library hides most of the details from you.  All you need to do is
break your code up into simple operation steps (addition, multiplication,
negation, copy, etc).  Then perform the steps one at a time, using
the temporary values in subsequent steps.  @xref{Instructions}, for
a complete list of all instructions that are supported by @code{libjit}.

Now that we have computed the desired result, we return it to the caller
using @code{jit_insn_return}:

@example
jit_insn_return(function, temp2);
@end example

We have completed the process of building the function body.  Now we
compile it into its executable form:

@example
jit_function_compile(function);
jit_context_build_end(context);
@end example

As a side-effect, this will discard all of the memory associated with
the values and instructions that we constructed while building the
function.  They are no longer required, because we now have the
executable form that we require.

We also unlock the context, because it is now safe for other threads
to access the function building process.

Up until this point, we haven't executed the @code{mul_add} function.
All we have done is build and compile it, ready for execution.  To execute it,
we call @code{jit_function_apply}:

@example
jit_int arg1, arg2, arg3;
void *args[3];
jit_int result;
...
arg1 = 3;
arg2 = 5;
arg3 = 2;
args[0] = &arg1;
args[1] = &arg2;
args[2] = &arg3;
jit_function_apply(function, args, &result);
printf("mul_add(3, 5, 2) = %d\n", (int)result);
@end example

We pass an array of pointers to @code{jit_function_apply}, each one
pointing to the corresponding argument value.  This gives us a very
general purpose mechanism for calling any function that may be
built and compiled using @code{libjit}.  If all went well, the
program should print the following:

@example
mul_add(3, 5, 2) = 17
@end example

You will notice that we used @code{jit_int} as the type of the arguments,
not @code{int}.  The @code{jit_int} type is guaranteed to be 32 bits
in size on all platforms, whereas @code{int} varies in size from platform
to platform.  Since we wanted our function to work the same everywhere,
we used a type with a predictable size.

If you really wanted the system @code{int} type, you would use
@code{jit_type_sys_int} instead of @code{jit_type_int} when you
created the function's signature.  The @code{jit_type_sys_int} type
is guaranteed to match the local system's @code{int} precision.

@noindent
Finally, we clean up the context and all of the memory that was used:

@example
jit_context_destroy(context);
@end example

@c -----------------------------------------------------------------------

@node Tutorial 2, Tutorial 3, Tutorial 1, Tutorials
@section Tutorial 2 - gcd
@cindex gcd tutorial

In this second tutorial, we implement the subtracting Euclidean
Greatest Common Divisor (GCD) algorithm over positive integers.
This tutorial demonstrates how to handle conditional branching
and function calls.  In C, the code for the @code{gcd} function
is as follows:

libjit/doc/libjit.texi  view on Meta::CPAN


We can now call this function with @code{jit_function_apply}, and the
system will automatically call @code{compile_mul_add} for us if the
function hasn't been built yet.  The contents of @code{compile_mul_add}
are fairly obvious:

@example
int compile_mul_add(jit_function_t function)
@{
    jit_value_t x, y, z;
    jit_value_t temp1, temp2;

    x = jit_value_get_param(function, 0);
    y = jit_value_get_param(function, 1);
    z = jit_value_get_param(function, 2);

    temp1 = jit_insn_mul(function, x, y);
    temp2 = jit_insn_add(function, temp1, z);

    jit_insn_return(function, temp2);
    return 1;
@}
@end example

When the on-demand compiler returns, @code{libjit} will call
@code{jit_function_compile} and then jump to the newly compiled code.
Upon the second and subsequent calls to the function, @code{libjit}
will bypass the on-demand compiler and call the compiled code directly.
Note that in case of on-demand compilation @code{libjit} automatically
locks and unlocks the corresponding context with
@code{jit_context_build_start} and @code{jit_context_build_end} calls.

Sometimes you may wish to force a commonly used function to
be recompiled, so that you can apply additional optimization.
To do this, you must set the "recompilable" flag just after the
function is first created:

@example
jit_function_t function;
...
function = jit_function_create(context, signature);
jit_function_set_recompilable(function);
jit_function_set_on_demand_compiler(function, compile_mul_add);
@end example

Once the function is compiled (either on-demand or up-front) its
intermediate representation built by @code{libjit} is discarded.
To force the function to be recompiled you need to build it again
and call @code{jit_function_compile} after that.  As always when
the function is built and compiled manually it is necessary
to take care of context locking:

@example
jit_context_build_start(context);
jit_function_get_on_demand_compiler(function)(function);
jit_function_compile(function);
jit_context_build_end(context);
@end example

After this, any existing references to the function will be redirected
to the new version.  However, if some thread is currently executing the
previous version, then it will keep doing so until the previous version
exits.  Only after that will subsequent calls go to the new version.

In this tutorial, we use the same on-demand compiler when we
recompile @code{mul_add}.  In a real program, you would probably call
@code{jit_function_set_on_demand_compiler} to set a new on-demand
compiler that performs greater levels of optimization.

If you no longer intend to recompile the function, you should call
@code{jit_function_clear_recompilable} so that @code{libjit} can
manage the function more efficiently from then on.

The exact conditions under which a function should be recompiled
are not specified by @code{libjit}.  It may be because the function
has been called several times and has reached some threshold.
Or it may be because some other function that it calls has become a
candidate for inlining.  It is up to the front end to decide when
recompilation is warranted, usually based on language-specific
heuristics.

@c -----------------------------------------------------------------------

@node Tutorial 4, Tutorial 5, Tutorial 3, Tutorials
@section Tutorial 4 - mul_add, C++ version
@cindex mul_add C++ tutorial

While @code{libjit} can be easily accessed from C++ programs using
the C API's, you may instead wish to use an API that better reflects
the C++ programming paradigm.  We demonstrate how to do this by rewriting
Tutorial 3 using the @code{libjitplus} library.

@noindent
To use the @code{libjitplus} library, we first include
the @code{<jit/jit-plus.h>} file:

@example
#include <jit/jit-plus.h>
@end example

This file incorporates all of the definitions from @code{<jit/jit.h>},
so you have full access to the underlying C API if you need it.

This time, instead of building the @code{mul_add} function with
@code{jit_function_create} and friends, we define a class to represent it:

@example
class mul_add_function : public jit_function
@{
public:
    mul_add_function(jit_context& context) : jit_function(context)
    @{
        create();
        set_recompilable();
    @}

    virtual void build();

protected:
    virtual jit_type_t create_signature();
@};



( run in 1.476 second using v1.01-cache-2.11-cpan-97f6503c9c8 )