issue #121 fix potential issue with lifetime of LLVM module created via the bindings API

gccjit-ravi534
Dibyendu Majumdar 7 years ago
parent 9dac99942a
commit 66d3e20d30

@ -472,7 +472,8 @@ class RaviJITFunction {
const std::string &name() const { return name_; }
llvm::Function *function() const { return function_; }
llvm::Module *module() const { return module_->module(); }
RaviJITModule *raviModule() const { return module_.get(); }
std::shared_ptr<RaviJITModule> raviModule() const { return module_; }
llvm::ExecutionEngine *engine() const { return module_->engine(); }
RaviJITState *owner() const { return module_->owner(); }
// This method retrieves the JITed function from the
@ -527,6 +528,10 @@ class RaviJITState {
// instruction; this is expensive!
bool tracehook_enabled_;
// Count of modules allocated
// Used to debug module deallocation
size_t allocated_modules_;
public:
RaviJITState();
~RaviJITState();
@ -561,6 +566,9 @@ class RaviJITState {
void set_gcstep(int value) { gc_step_ = value > 0 ? value : gc_step_; }
bool is_tracehook_enabled() const { return tracehook_enabled_; }
void set_tracehook_enabled(bool value) { tracehook_enabled_ = value; }
void incr_allocated_modules() { allocated_modules_++; }
void decr_allocated_modules() { allocated_modules_--; }
size_t allocated_modules() const { return allocated_modules_; }
};
// To optimise fornum loops

@ -5,6 +5,56 @@ Ravi Parsing and ByteCode Implementation Details
This document covers the enhancements to the Lua parser and byte-code generator.
The Ravi JIT implementation is described elsewhere.
Introduction
============
Since the reason for introducing optional static typing is to enhance performance primarily - not all types benefit from this capability. In fact it is quite hard to extend this to generic recursive structures such as tables without encurring significant overhead. For instance - even to represent a recursive type in the parser will require dynamic memory allocation and add great overhead to the parser.
From a performance point of view the only types that seem worth specializing are:
* integer (64-bit int)
* number (double)
* array of integers
* array of numbers
* table
Implementation Strategy
=======================
I want to build on existing Lua types rather than introducing completely new types to the Lua system. I quite like the minimalist nature of Lua. However, to make the execution efficient I am adding new type specific opcodes and enhancing the Lua parser/code generator to encode these opcodes only when types are known. The new opcodes will execute more efficiently as they will not need to perform type checks. Morever, type specific instructions will lend themselves to more efficient JIT compilation.
I am adding new opcodes that cover arithmetic operations, array operations, variable assignments, etc..
Modifications to Lua Bytecode structure
=======================================
An immediate issue is that the Lua bytecode structure has a 6-bit opcode which is insufficient to hold the various opcodes that I will need. Simply extending the size of this is problematic as then it reduces the space available to the operands A B and C. Furthermore the way Lua bytecodes work means that B and C operands must be 1-bit larger than A - as the extra bit is used to flag whether the operand refers to a constant or a register. (Thanks to Dirk Laurie for pointing this out).
I am amending the bit mapping in the 32-bit instruction to allow 9-bits for the byte-code, 7-bits for operand A, and 8-bits for operands B and C. This means that some of the Lua limits (maximum number of variables in a function, etc.) have to be revised to be lower than the default.
New OpCodes
===========
The new instructions are specialised for types, and also for register/versus constant. So for example ``OP_RAVI_ADDFI`` means add ``number`` and ``integer``. And ``OP_RAVI_ADDFF`` means add ``number`` and ``number``. The existing Lua opcodes that these are based on define which operands are used.
Example::
local i=0; i=i+1
Above standard Lua code compiles to::
[0] LOADK A=0 Bx=-1
[1] ADD A=0 B=0 C=-2
[2] RETURN A=0 B=1
We add type info using Ravi extensions::
local i:integer=0; i=i+1
Now the code compiles to::
[0] LOADK A=0 Bx=-1
[1] ADDII A=0 B=0 C=-2
[2] RETURN A=0 B=1
Above uses type specialised opcode ``OP_RAVI_ADDII``.
Type Information
================
The basic first step is to add type information to Lua.

@ -1,3 +1,4 @@
=========================
Ravi Programming Language
=========================
@ -12,23 +13,27 @@ My motivation is somewhat different - I want to enhance the VM to support more e
Of course there is also the fantastic `LuaJIT <http://luajit.org>`_ implementation. Ravi has a different goal compared to
LuaJIT. Ravi prioritizes ease of maintenance and support, language safety, and compatibility with Lua 5.3, over maximum performance. For more detailed comparison please refer to the documentation links below.
Goals
-----
* Optional static typing for Lua
.. contents:: Table of Contents
:depth: 1
Features
========
* Optional static typing
* Type specific bytecodes to improve performance
* Compatibility with Lua 5.3 (see Compatibility section below)
* `LLVM <http://www.llvm.org/>`_ powered JIT compiler
* Additionally a `libgccjit <https://gcc.gnu.org/wiki/JIT>`_ based alternative JIT compiler is also available
* LLVM bindings exposed in Lua
Documentation
--------------
=============
See `Ravi Documentation <http://the-ravi-programming-language.readthedocs.org/en/latest/index.html>`_.
As more stuff is built I will keep updating the documentation so please revisit for latest information.
Also see the slides I presented at the `Lua 2015 Workshop <http://www.lua.org/wshop15.html>`_.
JIT Implementation
++++++++++++++++++
==================
The LLVM JIT compiler is functional. The Lua and Ravi bytecodes currently implemented in LLVM are described in `JIT Status <http://the-ravi-programming-language.readthedocs.org/en/latest/ravi-jit-status.html>`_ page.
Ravi also provides an `LLVM binding <http://the-ravi-programming-language.readthedocs.org/en/latest/llvm-bindings.html>`_; this is still work in progress so please check documentation for the latest status.
@ -36,14 +41,14 @@ Ravi also provides an `LLVM binding <http://the-ravi-programming-language.readth
As of July 2015 the `libgccjit <http://the-ravi-programming-language.readthedocs.org/en/latest/ravi-jit-libgccjit.html>`_ based JIT implementation is also functional but some byte codes are not yet compiled, and featurewise this implementation is somewhat lagging behind the LLVM based implementation.
Performance Benchmarks
++++++++++++++++++++++
======================
For performance benchmarks please visit the `Ravi Performance Benchmarks <http://the-ravi-programming-language.readthedocs.org/en/latest/ravi-benchmarks.html>`_ page.
Ravi Extensions to Lua 5.3
--------------------------
==========================
Optional Static Typing
++++++++++++++++++++++
----------------------
Ravi allows you to annotate ``local`` variables and function parameters with static types. The supported types and the resulting behaviour are as follows:
``integer``
@ -160,7 +165,7 @@ Following library functions allow creation of array types of defined length.
creates an number array of specified size, and initializes with initial value. The return type is number[]. The size of the array cannot be changed dynamically, i.e. it is fixed to the initial specified size. This allows slices to be created on such arrays.
Type Assertions
+++++++++++++++
---------------
Ravi does not support defining new types, or structured types based on tables. This creates some practical issues when dynamic types are mixed with static types. For example::
local t = { 1,2,3 }
@ -181,7 +186,7 @@ The type assertion operator is a unary operator and binds to the expression foll
For a real example of how type assertions can be used, please have a look at the test program `gaussian2.lua <https://github.com/dibyendumajumdar/ravi/blob/master/ravi-tests/gaussian2.lua>`_
Array Slices
++++++++++++
------------
Since release 0.6 Ravi supports array slices. An array slice allows a portion of a Ravi array to be treated as if it is an array - this allows efficient access to the underlying array elements. Following new functions are available:
``table.slice(array, start_index, num_elements)``
@ -198,7 +203,7 @@ Each slice holds an internal reference to the underlying array to ensure that th
For an example use of slices please see the `matmul1.ravi <https://github.com/dibyendumajumdar/ravi/blob/master/ravi-tests/matmul1.ravi>`_ benchmark program in the repository. Note that this feature is highly experimental and not very well tested.
Examples
++++++++
--------
Example of code that works - you can copy this to the command line input::
function tryme()
@ -246,11 +251,11 @@ Another example using arrays. Here the function receives a parameter ``arr`` of
The ``table.numarray(n, initial_value)`` creates a ``number[]`` of specified size and initializes the array with the given initial value.
All type checks are at runtime
++++++++++++++++++++++++++++++
------------------------------
To keep with Lua's dynamic nature Ravi uses a mix of compile type checking and runtime type checks. However due to the dynamic nature of Lua, compilation happens at runtime anyway so effectually all checks are at runtime.
JIT Compilation
---------------
JIT API
-------
The LLVM based JIT compiler is functional. Most bytecodes other than bit-wise operators are JIT compiled when using LLVM, but there are restrictions as described in compatibility section below. Everything described below relates to using LLVM as the JIT compiler.
There are two modes of JIT compilation.
@ -285,12 +290,12 @@ A JIT api is available with following functions:
boundary; use this option only when you want to use the debug api to step through code line by line
Performance Notes
-----------------
=================
To obtain the best possible performance, types must be annotated so that Ravi's JIT compiler can generate efficient code.
Additionally function calls are expensive - as the JIT compiler cannot inline function calls, all function calls go via the Lua call protocol which has a large overhead. This is true for both Lua functions and C functions. For best performance avoid function calls inside loops.
Compatibility with Lua
----------------------
======================
Ravi should be able to run all Lua 5.3 programs in interpreted mode, but there are some differences:
* Ravi supports optional typing and enhanced types such as arrays (described above). Programs using these features cannot be run by standard Lua. However all types in Ravi can be passed to Lua functions; operations on Ravi arrays within Lua code will be subject to restrictions as described in the section above on arrays.
@ -317,8 +322,11 @@ When JIT compilation is enabled some things will not work:
* You cannot yield from a compiled function as compiled code does not support coroutines (issue 14); as a workaround Ravi will only execute JITed code from the main Lua thread; any secondary threads (coroutines) execute in interpreter mode.
* In JITed code tailcalls are implemented as regular calls so unlike Lua VM which supports infinite tail recursion JIT compiled code only supports tail recursion to a depth of about 110 (issue #17)
Build Dependencies - LLVM version
---------------------------------
Building Ravi
=============
Build Dependencies
------------------
* CMake
* LLVM 3.7 or 3.8 or 3.9
@ -357,8 +365,8 @@ Assuming that LLVM source has been extracted to ``$HOME/llvm-3.7.0.src`` I follo
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/LLVM -DLLVM_TARGETS_TO_BUILD="X86" ..
make install
Building Ravi
-------------
Building Ravi with JIT enabled
------------------------------
I am developing Ravi using Visual Studio 2015 Community Edition on Windows 8.1 64bit, gcc on Unbuntu 64-bit, and clang/Xcode on MAC OS X. I was also able to successfully build a Ubuntu version on Windows 10 using the newly released Ubuntu/Linux sub-system for Windows 10.
.. note:: Location of cmake files has moved in LLVM 3.9; the new path is ``$LLVM_INSTALL_DIR/lib/cmake/llvm``.
@ -393,8 +401,8 @@ Building without JIT
--------------------
You can omit ``-DLLVM_JIT=ON`` option above to build Ravi with a null JIT implementation.
Static Libraries
----------------
Building Static Libraries
-------------------------
By default the build generates a shared library for Ravi. You can choose to create a static library and statically linked executables by supplying the argument ``-DSTATIC_BUILD=ON`` to CMake.
Build Artifacts
@ -418,64 +426,17 @@ I test the build by running a modified version of Lua 5.3.3 test suite. These te
.. note:: To thoroughly test changes, you need to invoke CMake with ``-DCMAKE_BUILD_TYPE=Debug`` option. This turns on assertions, memory checking, and also enables an internal module used by Lua tests.
Work Plan
---------
* Feb-Jun 2015 - implement JIT compilation using LLVM
* Jun-Jul 2015 - libgccjit based alternative JIT
* 2016 priorties
* `IDE support (Visual Studio Code) <https://github.com/dibyendumajumdar/ravi/tree/master/vscode-debugger>`_
Roadmap
=======
* 2015 - Implemented JIT compilation using LLVM
* 2015 - Implemented libgccjit based alternative JIT
* 2016 - Implemented debugger for Ravi and Lua 5.3 for `Visual Studio Code <https://github.com/dibyendumajumdar/ravi/tree/master/vscode-debugger>`_
* 2017 - Main priorities are:
- Add compatibility to Lua 5.1 and 5.2 as far as possible
- Lua function inlining
- Improve performance of Ravi
License
-------
=======
MIT License for LLVM version.
Language Syntax - Future work
-----------------------------
Since the reason for introducing optional static typing is to enhance performance primarily - not all types benefit from this capability. In fact it is quite hard to extend this to generic recursive structures such as tables without encurring significant overhead. For instance - even to represent a recursive type in the parser will require dynamic memory allocation and add great overhead to the parser.
From a performance point of view the only types that seem worth specializing are:
* integer (64-bit int)
* number (double)
* array of integers
* array of numbers
Implementation Strategy
-----------------------
I want to build on existing Lua types rather than introducing completely new types to the Lua system. I quite like the minimalist nature of Lua. However, to make the execution efficient I am adding new type specific opcodes and enhancing the Lua parser/code generator to encode these opcodes only when types are known. The new opcodes will execute more efficiently as they will not need to perform type checks. Morever, type specific instructions will lend themselves to more efficient JIT compilation.
I am adding new opcodes that cover arithmetic operations, array operations, variable assignments, etc..
Modifications to Lua Bytecode structure
---------------------------------------
An immediate issue is that the Lua bytecode structure has a 6-bit opcode which is insufficient to hold the various opcodes that I will need. Simply extending the size of this is problematic as then it reduces the space available to the operands A B and C. Furthermore the way Lua bytecodes work means that B and C operands must be 1-bit larger than A - as the extra bit is used to flag whether the operand refers to a constant or a register. (Thanks to Dirk Laurie for pointing this out).
I am amending the bit mapping in the 32-bit instruction to allow 9-bits for the byte-code, 7-bits for operand A, and 8-bits for operands B and C. This means that some of the Lua limits (maximum number of variables in a function, etc.) have to be revised to be lower than the default.
New OpCodes
-----------
The new instructions are specialised for types, and also for register/versus constant. So for example ``OP_RAVI_ADDFI`` means add ``number`` and ``integer``. And ``OP_RAVI_ADDFF`` means add ``number`` and ``number``. The existing Lua opcodes that these are based on define which operands are used.
Example::
local i=0; i=i+1
Above standard Lua code compiles to::
[0] LOADK A=0 Bx=-1
[1] ADD A=0 B=0 C=-2
[2] RETURN A=0 B=1
We add type info using Ravi extensions::
local i:integer=0; i=i+1
Now the code compiles to::
[0] LOADK A=0 Bx=-1
[1] ADDII A=0 B=0 C=-2
[2] RETURN A=0 B=1
Above uses type specialised opcode ``OP_RAVI_ADDII``.

@ -602,11 +602,6 @@ static int pmain (lua_State *L) {
return 1;
}
#if 0
// For debugging
LUA_API int ravi_get_modulecount();
#endif
int main (int argc, char **argv) {
int status, result;
lua_State *L = luaL_newstate(); /* create state */
@ -632,10 +627,6 @@ int main (int argc, char **argv) {
result = lua_toboolean(L, -1); /* get result */
report(L, status);
lua_close(L);
#if 0
// For debugging - should be 0
fprintf(stderr, "Modules at exit %d\n", ravi_get_modulecount());
#endif
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}

@ -23,18 +23,6 @@
#include <ravijit.h>
#include "ravi_llvmcodegen.h"
#if 0
// For debugging
static int allocated_modules = 0;
extern "C" {
LUA_API int ravi_get_modulecount() {
return allocated_modules;
}
}
#endif
/*
* Implementation Notes:
* Each Lua function is compiled into an LLVM Module/Function
@ -60,7 +48,8 @@ RaviJITState::RaviJITState()
min_code_size_(150),
min_exec_count_(50),
gc_step_(300),
tracehook_enabled_(false) {
tracehook_enabled_(false),
allocated_modules_(0) {
// LLVM needs to be initialized else
// ExecutionEngine cannot be created
// This needs to be an atomic check although LLVM docs
@ -86,6 +75,7 @@ RaviJITState::RaviJITState()
// Destroy the JIT state freeing up any
// functions that were compiled
RaviJITState::~RaviJITState() {
assert(allocated_modules_ == 0);
delete types_;
delete context_;
}
@ -98,7 +88,6 @@ void RaviJITState::dump() { types_->dump(); }
static std::atomic_int module_id;
RaviJITModule::RaviJITModule(RaviJITState *owner)
: owner_(owner), engine_(nullptr), module_(nullptr) {
int myid = module_id++;
@ -109,17 +98,21 @@ RaviJITModule::RaviJITModule(RaviJITState *owner)
if (myid == 0) {
// Extra validation to check that the LLVM sizes match Lua sizes
llvm::DataLayout *layout = new llvm::DataLayout(module_);
//auto valueSize = layout->getTypeAllocSize(owner->types()->ValueT);
//auto valueSizeOf = sizeof(Value);
//auto TvalueSize = layout->getTypeAllocSize(owner->types()->TValueT);
//auto TvalueSizeOf = sizeof(TValue);
//printf("Value %d %d Tvalue %d %d\n", (int)valueSize, (int)valueSizeOf, (int)TvalueSize, (int)TvalueSizeOf);
// auto valueSize = layout->getTypeAllocSize(owner->types()->ValueT);
// auto valueSizeOf = sizeof(Value);
// auto TvalueSize = layout->getTypeAllocSize(owner->types()->TValueT);
// auto TvalueSizeOf = sizeof(TValue);
// printf("Value %d %d Tvalue %d %d\n", (int)valueSize, (int)valueSizeOf,
// (int)TvalueSize, (int)TvalueSizeOf);
assert(sizeof(Value) == layout->getTypeAllocSize(owner->types()->ValueT));
assert(sizeof(TValue) == layout->getTypeAllocSize(owner->types()->TValueT));
assert(sizeof(UTString) == layout->getTypeAllocSize(owner->types()->TStringT));
assert(sizeof(UTString) ==
layout->getTypeAllocSize(owner->types()->TStringT));
assert(sizeof(Udata) == layout->getTypeAllocSize(owner->types()->UdataT));
assert(sizeof(CallInfo) == layout->getTypeAllocSize(owner->types()->CallInfoT));
assert(sizeof(lua_State) == layout->getTypeAllocSize(owner->types()->lua_StateT));
assert(sizeof(CallInfo) ==
layout->getTypeAllocSize(owner->types()->CallInfoT));
assert(sizeof(lua_State) ==
layout->getTypeAllocSize(owner->types()->lua_StateT));
delete layout;
}
#if defined(_WIN32) && (!defined(_WIN64) || LLVM_VERSION_MINOR < 7)
@ -141,11 +134,10 @@ RaviJITModule::RaviJITModule(RaviJITState *owner)
std::string errStr;
builder.setErrorStr(&errStr);
engine_ = builder.create();
#if 0
allocated_modules++;
#endif
owner->incr_allocated_modules();
if (!engine_) {
fprintf(stderr, "FATAL ERROR: could not create ExecutionEngine: %s\n", errStr.c_str());
fprintf(stderr, "FATAL ERROR: could not create ExecutionEngine: %s\n",
errStr.c_str());
abort();
return;
}
@ -158,8 +150,8 @@ RaviJITModule::~RaviJITModule() {
// if engine was created then we don't need to delete the
// module as it would have been deleted by the engine
delete module_;
owner_->decr_allocated_modules();
#if 0
allocated_modules--;
//fprintf(stderr, "module destroyed\n");
#endif
}
@ -191,7 +183,7 @@ RaviJITFunction::RaviJITFunction(lua_CFunction *p,
RaviJITFunction::~RaviJITFunction() {
// Remove this function from parent
//fprintf(stderr, "function destroyed\n");
// fprintf(stderr, "function destroyed\n");
module_->removeFunction(this);
}
@ -279,7 +271,7 @@ void RaviJITModule::runpasses(bool dumpAsm) {
}
MPM->run(*module_);
// Note that in 3.7 this flus appears to have no effect
// Note that in 3.7 this flus appears to have no effect
#if LLVM_VERSION_MINOR <= 7
formatted_stream.flush();
#endif
@ -321,8 +313,7 @@ llvm::Function *RaviJITModule::addExternFunction(llvm::FunctionType *type,
void *address,
const std::string &name) {
auto fn = external_symbols_.find(name);
if (fn != external_symbols_.end())
return fn->second;
if (fn != external_symbols_.end()) return fn->second;
llvm::Function *f = llvm::Function::Create(
type, llvm::Function::ExternalLinkage, name, module_);
f->setDoesNotThrow();
@ -419,7 +410,7 @@ int raviV_compile(struct lua_State *L, struct Proto *p,
// And put them all in one module
// Returns true if compilation was successful
int raviV_compile_n(struct lua_State *L, struct Proto *p[], int n,
ravi_compile_options_t *options) {
ravi_compile_options_t *options) {
global_State *G = G(L);
int count = 0;
auto module = std::make_shared<ravi::RaviJITModule>(G->ravi_state->jit);

@ -35,8 +35,8 @@ extern "C" {
#include "lauxlib.h"
// Utility to extract a boolean field from a table
bool l_table_get_bool(lua_State *L, int idx, const char *key, bool *result,
bool default_value) {
static bool l_table_get_bool(lua_State *L, int idx, const char *key,
bool *result, bool default_value) {
bool rc = false;
lua_pushstring(L, key);
lua_gettable(L, idx); /* get table[key] */
@ -194,7 +194,13 @@ struct InstructionHolder {
};
struct ModuleHolder {
llvm::Module *M;
std::shared_ptr<ravi::RaviJITModule> M;
ModuleHolder(const std::shared_ptr<ravi::RaviJITModule> &module) {
M = module;
printf("ModuleHolder created\n");
}
~ModuleHolder() { printf("ModuleHolder destroyed\n"); }
};
struct PhiNodeHolder {
@ -221,11 +227,20 @@ static int collect_LLVM_irbuilder(lua_State *L) {
return 0;
}
static void alloc_LLVM_module(lua_State *L, llvm::Module *M) {
static void alloc_LLVM_module(lua_State *L,
const std::shared_ptr<ravi::RaviJITModule> &M) {
ModuleHolder *mh = (ModuleHolder *)lua_newuserdata(L, sizeof(ModuleHolder));
raviL_getmetatable(L, LLVM_module);
lua_setmetatable(L, -2);
mh->M = M;
new (mh) ModuleHolder(M);
}
/* __gc for ModuleHolder */
static int collect_LLVM_module(lua_State *L) {
ModuleHolder *mh = check_LLVM_module(L, 1);
printf("Module released: usecount %d\n", mh->M.use_count());
mh->~ModuleHolder();
return 0;
}
static void alloc_LLVM_type(lua_State *L, llvm::Type *t) {
@ -610,8 +625,8 @@ static int context_new_lua_CFunction(lua_State *L) {
}
static int func_getmodule(lua_State *L) {
llvm::Function *func = get_function(L, 1);
alloc_LLVM_module(L, func->getParent());
MainFunctionHolder *f = check_LLVM_mainfunction(L, 1);
alloc_LLVM_module(L, f->func->raviModule());
return 1;
}
@ -622,10 +637,11 @@ static int module_newfunction(lua_State *L) {
bool extern_linkage = false;
if (lua_istable(L, 4))
l_table_get_bool(L, 4, "extern", &extern_linkage, false);
llvm::Function *f = llvm::Function::Create(
fth->type, extern_linkage ? llvm::Function::ExternalLinkage
: llvm::Function::InternalLinkage,
name, mh->M);
llvm::Function *f =
llvm::Function::Create(fth->type,
extern_linkage ? llvm::Function::ExternalLinkage
: llvm::Function::InternalLinkage,
name, mh->M->module());
alloc_LLVM_function(L, f);
return 1;
}
@ -1327,6 +1343,8 @@ LUAMOD_API int raviopen_llvmluaapi(lua_State *L) {
raviL_newmetatable(L, LLVM_module, LLVM_module);
lua_pushstring(L, LLVM_module);
lua_setfield(L, -2, "type");
lua_pushcfunction(L, collect_LLVM_module);
lua_setfield(L, -2, "__gc");
lua_pushvalue(L, -1); /* push metatable */
lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */
luaL_setfuncs(L, module_methods, 0);

Loading…
Cancel
Save