refactor and update doc

pull/81/head
Dibyendu Majumdar 9 years ago
parent 15c466561b
commit 5bd0058b28

@ -1,13 +1,11 @@
=============
Lua Internals
=============
As I learn more about Lua internals I will make notes here.
=========================================
Lua Parsing and Code Generation Internals
=========================================
Stack and Registers
===================
There are two stacks.
The ``Callinfo`` stack tracks activation frames.
Lua employs two stacks.
The ``Callinfo`` stack tracks activation frames.
There is the secondary stack ``L->stack`` that is an array of ``TValue`` objects. The ``Callinfo`` objects index into this array. Registers are basically slots in the ``L->stack`` array.
When a function is called - the stack is setup as follows::

@ -1,8 +1,9 @@
===========================
Ravi Implementation Details
===========================
================================================
Ravi Parsing and ByteCode Implementation Details
================================================
As I progress with Ravi I will document the design and implementation details here.
This document covers the enhancements to the Lua parser and byte-code generator.
The Ravi JIT implementation is described elsewhere.
Type Information
================

@ -71,8 +71,8 @@ The ``ravi_jit`` member is initialized in ``lfunc.c``::
GCObject *o = luaC_newobj(L, LUA_TPROTO, sizeof(Proto));
Proto *f = gco2p(o);
f->k = NULL;
/* code ommitted */
f->ravi_jit.jit_data = NULL;
/* code ommitted */
f->ravi_jit.jit_data = NULL;
f->ravi_jit.jit_function = NULL;
f->ravi_jit.jit_status = 0; /* not compiled */
return f;
@ -130,23 +130,44 @@ When a Lua Function is called it goes through ``luaD_precall()`` in ``ldo.c``. T
lua_assert(ci->top <= L->stack_last);
ci->u.l.savedpc = p->code; /* starting point */
ci->callstatus = CIST_LUA;
ci->jitstatus = 0;
L->top = ci->top;
luaC_checkGC(L); /* stack grow uses memory */
if (L->hookmask & LUA_MASKCALL)
callhook(L, ci);
if (p->ravi_jit.jit_status == 0) {
/* not compiled */
raviV_compile(L, p);
}
if (p->ravi_jit.jit_status == 2) {
/* compiled */
lua_assert(p->ravi_jit.jit_function != NULL);
(*p->ravi_jit.jit_function)(L);
lua_assert(L->ci == prevci);
ci = L->ci;
lua_assert(isLua(ci));
lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL);
return 1;
if (compile) {
if (p->ravi_jit.jit_status == 0) {
/* not compiled */
raviV_compile(L, p, 0);
}
if (p->ravi_jit.jit_status == 2) {
/* compiled */
lua_assert(p->ravi_jit.jit_function != NULL);
ci->jitstatus = 1;
/* As JITed function is like a C function
* employ the same restrictions on recursive
* calls as for C functions
*/
if (++L->nCcalls >= LUAI_MAXCCALLS) {
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
/* Disable YIELDs - so JITed functions cannot
* yield
*/
L->nny++;
(*p->ravi_jit.jit_function)(L);
L->nny--;
L->nCcalls--;
lua_assert(L->ci == prevci);
/* Return a different value from 1 to
* allow luaV_execute() to distinguish between
* JITed function and true C function
*/
return 2;
}
}
return 0;
}
@ -158,5 +179,5 @@ When a Lua Function is called it goes through ``luaD_precall()`` in ``ldo.c``. T
}
Note that the above returns 1 if compiled function is called so that the behaviour in ``lvm.c`` is similar to that when a C function is called.
Note that the above returns 2 if compiled Lua function is called. The behaviour in ``lvm.c`` is similar to that when a C function is called.

@ -2,41 +2,4 @@
JIT Compilation for Ravi
========================
Introduction
------------
Lua's performance is pretty good as an interpreter. To get real performance improvements we need to switch paradigms and get into JIT compilation. One of the main goals of the optional typing in Ravi is to allow better JIT code generation as type information can be exploited. It is also to allow a more conventional JIT compiler rather than the high-tech trace compilers used in Luajit.
Approach
--------
Primary reason for Ravi's existence is that it needs to be implemented using technology that does not require us writing assembly language code for each platform. The other goal is to allow long term support and maintainability of the JIT compiler. Given these considerations my current plan is to use LLVM as the JIT compilation framework.
Status
------
Project kicked off Feb 2015. I am new to this so progress will be slow. I would like to get this compiler built and working by first half of 2015. As I make progress I will document the results here.
Preparing for LLVM
------------------
I am using Windows as my primary development platform but I also test on Linux using a VM. The first step appears to be to build LLVM from source as the only binary distribution is for CLANG.
I am using LLVM 3.5.1 release at present. I downloaded the source archive, uncompressed and using CMake GUI created the build configuration. Only item I changed ``CMAKE_INSTALL_PREFIX`` which I set to ``c:\LLVM``.
Note: I had to build the Release version for Ravi to link properly.
After modifying Ravi's ``CMakeLists.txt`` I invoked the cmake config as follows::
C:\github\ravi\build>cmake -DLLVM_DIR=c:\LLVM\share\llvm\cmake -G "Visual Studio 12 Win64" ..
On Ubuntu I found that the official LLVM distributions don't work with CMake. The CMake config files appear to be broken.
So I ended up downloading and building LLVM 3.5.1 from source and that worked. I used the same approach as on Windows - i.e., set ``CMAKE_INSTALL_PREFIX`` using ``cmake-gui`` to ``~/LLVM`` and that was about it.
The command to create makefiles was as follows::
cmake -DLLVM_DIR=/home/user/LLVM/share/llvm/cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" ..
Links
-----
* `Mapping High Level Constructs to LLVM IR <http://llvm.lyngvig.org/Articles/Mapping-High-Level-Constructs-to-LLVM-IR>`_
* `IRBuilder Sample <https://github.com/eliben/llvm-clang-samples/blob/master/src_llvm/experimental/build_llvm_ir.cpp>`_
* `Using MCJIT with Kaleidoscope <http://blog.llvm.org/2013/07/using-mcjit-with-kaleidoscope-tutorial.html>`_
Moved to ravi-jit-status.rst

@ -1,9 +1,60 @@
Ravi JIT Compilation Status
===========================
Introduction
------------
Ravi uses LLVM for JIT compilation.
Benefits of using LLVM
----------------------
* LLVM has a well documented intermediate representation called LLVM IR.
* The LLVM ``IRBuilder`` implements type checks so that when LLVM code is being generated, basic type errors are caught by the builder.
* LLVM provides a verifier to check that the generated IR is valid. This allows the IR to be validated prior to machine code generation.
* All of the LLVM optimization passes can be used.
* The Clang compiler supports generating LLVM IR so that if you want to know what the LLVM IR should look like for a parycular piece of code, you can write a small C snippet and have Clang generate the IR for you.
* There is great momentum behind LLVM.
* The LLVM license is not based on GPL, so it is not viral.
* LLVM is much better documented than other products that aim to cover similar ground.
* LLVM's API is well designed and has a layered architecture.
Drawbacks of LLVM
-----------------
* LLVM is huge in size. Lua on its own is tiny - but when linked to LLVM the resulting binary is a monster.
* There is a cost to compiling in LLVM so the benefit of compilation accrues only when a Lua function will be used again and again.
* LLVM cannot be linked as a shared library on Windows and a shared library configuration is not recommended on other platforms as well.
* LLVM's API keeps changing so that with every release of LLVM one has to revise the way it is used.
The Architecture of Ravi's JIT Compilation
------------------------------------------
* The unit of compilation is a Lua function
* Each Lua function is compiled to a Module/Function in LLVM parlance
* The compiled code is attached to the Lua function prototype
* The compiled code is garbage collected as normal by Lua
* The Lua runtime coordinates function calls - so anytime a Lua function is called it goes via the Lua infrastructure.
* The decision to call a JIT compiled version is made in the Lua Infrastructure (specifically in ``luaD_precall()`` function in ``ldo.c``)
* The JIT compiler translates Lua/Ravi bytecode to LLVM IR - i.e. it does not translate Lua source code.
* There is no inlining of Lua functions.
* Generally the JIT compiler implements the same instructions as in ``lvm.c`` - however for some bytecodes the code calls a C function rather than generating inline IR. These opcodes are OP_LOADNIL, OP_NEWTABLE, OP_RAVI_NEWARRAYINT, OP_RAVI_NEWARRAYFLT, OP_SETLIST, OP_CONCAT, OP_CLOSURE, OP_VARARG.
* Ravi represents Lua values as done by Lua 5.3 - i.e. in a 16 byte structure. In future this could change to a more optimized structure.
* Ravi compiler generates type specifc opcodes which result in simpler and higher performance LLVM IR.
Limitations of JIT compilation
------------------------------
* Coroutines are not supported - JITed functions cannot yield
* The Debug API relies upon a field called ``savedpc`` which tracks the current instruction being executed by Lua interpreter. As this is not updated by the JIT code the Debug API can only provide a subset of normal functionality. The Debug API is not yet fully tested.
* The Lua VM supports infinite tail recursion. The JIT compiler treats OP_TAILCALL as normal OP_CALL so that recursion is limited to about 110 levels.
* The Lua C API has not yet been tested against the Ravi extensions - especially static typing and array types. Do not use the C API for now - as you could break the type system of Ravi.
* Bit-wise operators are not yet JIT compiled.
Future Performance Enhancements
-------------------------------
The main area of enhancement is to provide specialised versions of Lua FORNUM loops so that the generated code is more efficient and is moreover recognised by LLVM as a loop.
JIT Status of Lua/Ravi Bytecodes
---------------------------------
The JIT compilation status of the Lua and Ravi bytecodes are given below.
This information was last updated on 2nd April 2015. As new bytecodes are being added to the JIT compiler on a regular basis
This information was last updated on 3rd April 2015. As new bytecodes are being added to the JIT compiler on a regular basis
the status information below may be slightly out of date.
Note that if a Lua functions contains a bytecode that cannot be be JITed then the function cannot be JITed.
@ -191,3 +242,27 @@ Note that if a Lua functions contains a bytecode that cannot be be JITed then th
| OP_RAVI_SETTABLE_AF | YES | R(A)[RK(B)] := RK(C) where RK(B) is an integer |
| | | R(A) is array of numbers, and RK(C) is a number |
+-------------------------+----------+--------------------------------------------------+
Ravi's JIT compiler source
--------------------------
The LLVM JIT implementation is in following sources:
* ravillvm.h - includes LLVM headers and defines the generic JIT State and Function interfaces
* ravijit.h - defines the JIT API
* ravi_llvmcodegen.h - defines the types used by the code generator
* ravijit.cpp - basic LLVM infrastructure and Ravi API definition
* ravi_llvmtypes.cpp - contains LLVM type definitions for Lua objects
* ravi_llvmcodegen.cpp - LLVM JIT compiler - main driver for compiling Lua bytecodes into LLVM IR
* ravi_llvmload.cpp - implements OP_LOADK and OP_MOVE, and related operations, also OP_LOADBOOL
* ravi_llvmcomp.cpp - implements OP_EQ, OP_LT, OP_LE, OP_TEST and OP_TESTSET.
* ravi_llvmreturn.cpp - implements OP_RETURN
* ravi_llvmforprep.cpp - implements OP_FORPREP
* ravi_llvmforloop.cpp - implements OP_FORLOOP
* ravi_llvmtforcall.cpp - implements OP_TFORCALL and OP_TFORLOOP
* ravi_llvmarith1.cpp - implements various type specialized arithmetic operations - these are Ravi extensions
* ravi_llvmarith2.cpp - implements Lua opcodes such as OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_POW, OP_IDIV, OP_MOD, OP_UNM
* ravi_llvmcall.cpp - implements OP_CALL, OP_JMP
* ravi_llvmtable.cpp - implements OP_GETTABLE, OP_SETTABLE and various other table operations, OP_SELF, and also upvalue operations
* ravi_llvmrest.cpp - OP_CLOSURE, OP_VARARG, OP_CONCAT

@ -22,6 +22,7 @@ The LLVM JIT implementation is in following sources:
* ravi_llvmreturn.cpp - implements OP_RETURN
* ravi_llvmforprep.cpp - implements OP_FORPREP
* ravi_llvmforloop.cpp - implements OP_FORLOOP
* ravi_llvmtforcall.cpp - implements OP_TFORCALL and OP_TFORLOOP
* ravi_llvmarith1.cpp - implements various type specialized arithmetic operations - these are Ravi extensions
* ravi_llvmarith2.cpp - implements Lua opcodes such as OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_POW, OP_IDIV, OP_MOD, OP_UNM
* ravi_llvmcall.cpp - implements OP_CALL, OP_JMP

@ -380,21 +380,28 @@ int luaD_precall (lua_State *L, StkId func, int nresults, int compile) {
/* compiled */
lua_assert(p->ravi_jit.jit_function != NULL);
ci->jitstatus = 1;
/* As JITed function is like a C function
* employ the same restrictions on recursive
* calls as for C functions
*/
if (++L->nCcalls >= LUAI_MAXCCALLS) {
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
/* Disable YIELDs - so JITed functions cannot
* yield
*/
L->nny++;
(*p->ravi_jit.jit_function)(L);
L->nny--;
L->nCcalls--;
lua_assert(L->ci == prevci);
ci = L->ci;
lua_assert(isLua(ci));
/* Return a different value from 1 to allow luaV_execute() to distinguish between JITed function and true C function*/
/* Return a different value from 1 to
* allow luaV_execute() to distinguish between
* JITed function and true C function
*/
return 2;
}
}

@ -22,9 +22,16 @@
******************************************************************************/
#include "ravi_llvmcodegen.h"
/*
* Implementation Notes:
* Each Lua function is compiled into an LLVM Module/Function
* This strategy allows functions to be garbage collected as normal by Lua
*/
namespace ravi {
// TODO - should probably be an atomic int
// This is just to avoid initailizing LLVM repeatedly -
// see below
static std::atomic_int init;
RaviJITState *RaviJITFunctionImpl::owner() const { return owner_; }
@ -32,7 +39,7 @@ RaviJITState *RaviJITFunctionImpl::owner() const { return owner_; }
RaviJITStateImpl::RaviJITStateImpl()
: context_(llvm::getGlobalContext()), auto_(false), enabled_(true),
opt_level_(2), size_level_(0) {
// Unless following three lines are not executed then
// LLVM needs to be initialized else
// ExecutionEngine cannot be created
// This should ideally be an atomic check but because LLVM docs
// say that it is okay to call these functions more than once we
@ -46,7 +53,7 @@ RaviJITStateImpl::RaviJITStateImpl()
triple_ = llvm::sys::getProcessTriple();
#ifdef _WIN32
// On Windows we get compilation error saying incompatible object format
// Reading posts on mailining lists I found that the issue is that COEFF
// Reading posts on mailing lists I found that the issue is that COEFF
// format is not supported and therefore we need to set -elf as the object
// format
triple_ += "-elf";
@ -144,36 +151,40 @@ RaviJITFunctionImpl::~RaviJITFunctionImpl() {
void *RaviJITFunctionImpl::compile() {
// module_->dump();
// We use the PassManagerBuilder to setup optimization
// passes - the PassManagerBuilder allows easy configuration of
// typical C/C++ passes corresponding to O0, O1, O2, and O3 compiler options
llvm::PassManagerBuilder pmb;
pmb.OptLevel = owner_->get_optlevel();
pmb.SizeLevel = owner_->get_sizelevel();
// Create a function pass manager for this engine
llvm::FunctionPassManager *FPM = new llvm::FunctionPassManager(module_);
{
// Create a function pass manager for this engine
std::unique_ptr<llvm::FunctionPassManager> FPM(
new llvm::FunctionPassManager(module_));
// Set up the optimizer pipeline. Start with registering info about how the
// target lays out data structures.
#if LLVM_VERSION_MINOR > 5
// LLVM 3.6.0 change
module_->setDataLayout(engine_->getDataLayout());
FPM->add(new llvm::DataLayoutPass());
// LLVM 3.6.0 change
module_->setDataLayout(engine_->getDataLayout());
FPM->add(new llvm::DataLayoutPass());
#else
auto target_layout = engine_->getTargetMachine()->getDataLayout();
module_->setDataLayout(target_layout);
FPM->add(new llvm::DataLayoutPass(*engine_->getDataLayout()));
// LLVM 3.5.0
auto target_layout = engine_->getTargetMachine()->getDataLayout();
module_->setDataLayout(target_layout);
FPM->add(new llvm::DataLayoutPass(*engine_->getDataLayout()));
#endif
llvm::PassManagerBuilder pmb;
pmb.OptLevel = owner_->get_optlevel();
pmb.SizeLevel = owner_->get_sizelevel();
pmb.populateFunctionPassManager(*FPM);
FPM->doInitialization();
FPM->run(*function_);
delete FPM;
llvm::PassManager *MPM = new llvm::PassManager();
pmb.populateModulePassManager(*MPM);
MPM->run(*module_);
delete MPM;
pmb.populateFunctionPassManager(*FPM);
FPM->doInitialization();
FPM->run(*function_);
}
// module_->dump();
{
std::unique_ptr<llvm::PassManager> MPM(new llvm::PassManager());
pmb.populateModulePassManager(*MPM);
MPM->run(*module_);
}
if (ptr_)
return ptr_;
@ -240,6 +251,7 @@ int raviV_initjit(struct lua_State *L) {
return 0;
}
// Free up the JIT State
void raviV_close(struct lua_State *L) {
global_State *G = G(L);
if (G->ravi_state == NULL)
@ -249,6 +261,11 @@ void raviV_close(struct lua_State *L) {
free(G->ravi_state);
}
// Compile a Lua function
// If JIT is turned off then compilation is skipped
// Compilation occurs if either auto compilation is ON or
// a manual compilation request was made
// Returns true if compilation was successful
int raviV_compile(struct lua_State *L, struct Proto *p, int manual_request) {
global_State *G = G(L);
if (G->ravi_state == NULL)
@ -261,6 +278,8 @@ int raviV_compile(struct lua_State *L, struct Proto *p, int manual_request) {
return p->ravi_jit.jit_status == 2;
}
// Free the JIT compiled function
// Note that this is called by the garbage collector
void raviV_freeproto(struct lua_State *L, struct Proto *p) {
if (p->ravi_jit.jit_status == 2) /* compiled */ {
ravi::RaviJITFunction *f =
@ -273,6 +292,7 @@ void raviV_freeproto(struct lua_State *L, struct Proto *p) {
}
}
// Dump the LLVM IR
void raviV_dumpllvmir(struct lua_State *L, struct Proto *p) {
if (p->ravi_jit.jit_status == 2) /* compiled */ {
ravi::RaviJITFunction *f =
@ -293,7 +313,7 @@ static int ravi_is_compiled(lua_State *L) {
return 1;
}
// Trt to JIT compile the given function
// Try to JIT compile the given function
static int ravi_compile(lua_State *L) {
int n = lua_gettop(L);
luaL_argcheck(L, n == 1, 1, "1 argument expected");
@ -326,6 +346,7 @@ static int ravi_dump_llvmir(lua_State *L) {
return 0;
}
// Turn on/off auto JIT compilation
static int ravi_auto(lua_State *L) {
global_State *G = G(L);
int n = lua_gettop(L);
@ -341,6 +362,7 @@ static int ravi_auto(lua_State *L) {
return 1;
}
// Turn on/off the JIT compiler
static int ravi_jitenable(lua_State *L) {
global_State *G = G(L);
int n = lua_gettop(L);
@ -356,6 +378,7 @@ static int ravi_jitenable(lua_State *L) {
return 1;
}
// Set LLVM optimization level
static int ravi_optlevel(lua_State *L) {
global_State *G = G(L);
int n = lua_gettop(L);
@ -371,6 +394,7 @@ static int ravi_optlevel(lua_State *L) {
return 1;
}
// Set LLVM code size level
static int ravi_sizelevel(lua_State *L) {
global_State *G = G(L);
int n = lua_gettop(L);

Loading…
Cancel
Save