diff --git a/CMakeLists.txt b/CMakeLists.txt index c022204..24c008c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ option(STATIC_BUILD "Build static version of Ravi, default is OFF" OFF) option(COMPUTED_GOTO "Controls whether the interpreter switch will use computed gotos on gcc/clang, default is ON" ON) option(LTESTS "Controls whether ltests are enabled in Debug mode; note requires Debug build" ON) option(ASAN "Controls whether address sanitizer should be enabled" OFF) +option(RAVICOMP "Controls whether to link in RaviComp" OFF) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") # By default on non-Windows platforms we enable MIR JIT if (NOT WIN32 @@ -66,6 +69,7 @@ set(C2MIR_SRCS mir/c2mir/c2mir.c) set(MIR_JIT_SRCS src/ravi_mirjit.c) set(NO_JIT_SRCS src/ravi_nojit.c) set(LUA_CMD_SRCS src/lua.c) +set(RAVICOMP_SRCS src/ravi_complib.c) file(GLOB RAVI_HEADERS "${PROJECT_SOURCE_DIR}/include/*.h") if (COMPUTED_GOTO AND NOT MSVC) @@ -79,16 +83,15 @@ endif () include(CheckCCompilerFlag) check_c_compiler_flag("-march=native" COMPILER_OPT_ARCH_NATIVE_SUPPORTED) if (COMPILER_OPT_ARCH_NATIVE_SUPPORTED AND NOT CMAKE_C_FLAGS MATCHES "-march=") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") -endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") +endif () if (ASAN) set(CMAKE_REQUIRED_FLAGS "-fsanitize=address") check_c_compiler_flag("-fsanitize=address" COMPILER_ASAN_SUPPORTED) if (COMPILER_ASAN_SUPPORTED AND NOT CMAKE_C_FLAGS_DEBUG MATCHES "-fsanitize=address") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") - endif() -endif() - + endif () +endif () if (LLVM_JIT) find_package(LLVM REQUIRED CONFIG) @@ -132,15 +135,33 @@ else () # CLion seems unable to handle include paths set on sources include_directories("${CMAKE_SOURCE_DIR}/mir;${CMAKE_SOURCE_DIR}/mir/c2mir") endif () - else() + else () set(JIT_SRCS ${NO_JIT_SRCS}) endif () endif () +if (RAVICOMP) + # Need MIR_JIT for the compiler add-on + find_package(RaviComp REQUIRED) + set(ADDON_SRCS ${RAVICOMP_SRCS}) + set_property(SOURCE ${RAVICOMP_SRCS} + APPEND + PROPERTY INCLUDE_DIRECTORIES ${RAVICOMP_INCLUDE_DIRS}) + if (MIR_JIT) + set_property(SOURCE ${RAVICOMP_SRCS} + APPEND + PROPERTY INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mir;${CMAKE_SOURCE_DIR}/mir/c2mir") + endif () + if ($ENV{CLION_IDE}) + # CLion seems unable to handle include paths set on sources + include_directories(${RAVICOMP_INCLUDE_DIRS}) + endif () +endif () + # IDE stuff if (MSVC OR APPLE) source_group("Ravi Headers" FILES ${RAVI_HEADERS}) - source_group("Ravi Source Files" FILES ${LUA_CORE_SRCS} ${LUA_LIB_SRCS} ${JIT_SRCS}) + source_group("Ravi Source Files" FILES ${LUA_CORE_SRCS} ${LUA_LIB_SRCS} ${JIT_SRCS} ${ADDON_SRCS}) endif () # Misc setup @@ -212,6 +233,7 @@ if (LLVM_JIT) message(STATUS "LLVM_LIBS ${LLVM_LIBS}") endif () + set(LIBRAVI_NAME libravi) #Main library @@ -219,8 +241,9 @@ add_library(${LIBRAVI_NAME} ${LIBRAVI_BUILD_TYPE} ${RAVI_HEADERS} ${LUA_LIB_SRCS} ${LUA_CORE_SRCS} - ${JIT_SRCS}) -target_link_libraries(${LIBRAVI_NAME} ${EXTRA_LIBRARIES} ${LLVM_LIBS} ${MIRJIT_LIBRARIES}) + ${JIT_SRCS} + ${ADDON_SRCS}) +target_link_libraries(${LIBRAVI_NAME} ${EXTRA_LIBRARIES} ${LLVM_LIBS} ${MIRJIT_LIBRARIES} ${RAVICOMP_LIBRARIES}) # Main Ravi executable add_executable(ravi ${LUA_CMD_SRCS}) @@ -283,6 +306,13 @@ if (NOT STATIC_BUILD) else () set_target_properties(${LIBRAVI_NAME} PROPERTIES PREFIX "") endif () +if (RAVICOMP) + set_property( + TARGET ${LIBRAVI_NAME} + APPEND + PROPERTY COMPILE_DEFINITIONS "USE_RAVICOMP=1") + set(USE_RAVICOMP 1) +endif () if (APPLE) set_property( TARGET ${LIBRAVI_NAME} libravinojit_static diff --git a/cmake/FindRaviComp.cmake b/cmake/FindRaviComp.cmake new file mode 100644 index 0000000..3a4d6da --- /dev/null +++ b/cmake/FindRaviComp.cmake @@ -0,0 +1,15 @@ +find_path(RAVICOMP_INCLUDE_DIRS ravi_compiler.h + PATHS + c:/Software/ravicomp/include/ravicomp + ~/Software/ravicomp/include/ravicomp + NO_DEFAULT_PATH + ) + +find_library(RAVICOMP_LIBRARIES + NAMES ravicomp + PATHS + c:/Software/ravicomp/lib + ~/Software/ravicomp/lib + ~/Software/ravicomp/lib64 + ) + diff --git a/include/lualib.h b/include/lualib.h index 1579358..50f7dac 100644 --- a/include/lualib.h +++ b/include/lualib.h @@ -54,8 +54,10 @@ LUAMOD_API int (luaopen_package) (lua_State *L); #define LUA_RAVILIBNAME "ravi" LUAMOD_API int (raviopen_jit)(lua_State *L); -#define LUA_ASTLIBNAME "ast" -LUAMOD_API int (raviopen_ast_library)(lua_State *L); +#define LUA_RAVICOMPLIBNAME "compiler" +LUAMOD_API int (raviopen_compiler)(lua_State *L); + + /* open all previous libraries */ LUALIB_API void (luaL_openlibs) (lua_State *L); diff --git a/ravi-config.h.in b/ravi-config.h.in index 0e21edc..bf82f93 100644 --- a/ravi-config.h.in +++ b/ravi-config.h.in @@ -5,5 +5,6 @@ #cmakedefine USE_LLVM @USE_LLVM@ #cmakedefine USE_OMRJIT @USE_OMRJIT@ #cmakedefine USE_MIRJIT @USE_MIRJIT@ +#cmakedefine USE_RAVICOMP @USE_RAVICOMP@ #endif //_REDUKTI_RAVI_CONFIG_H_IN_H diff --git a/src/linit.c b/src/linit.c index d3f44b3..d2c2ac4 100644 --- a/src/linit.c +++ b/src/linit.c @@ -54,6 +54,9 @@ static const luaL_Reg loadedlibs[] = { {LUA_BITLIBNAME, luaopen_bit32}, #endif {LUAJIT_BITLIBNAME, luaopen_bit }, +#if defined(USE_RAVICOMP) + {LUA_RAVICOMPLIBNAME, raviopen_compiler }, +#endif {NULL, NULL} }; diff --git a/src/ravi_complib.c b/src/ravi_complib.c new file mode 100644 index 0000000..d245245 --- /dev/null +++ b/src/ravi_complib.c @@ -0,0 +1,263 @@ +#include "ravi_api.h" + +#define LUA_CORE + +#include "ravi_mirjit.h" + +#include "lua.h" +#include "lapi.h" +#include "lauxlib.h" +#include "lfunc.h" +#include "lmem.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + +#include + +struct CompilerContext { + lua_State* L; + ravi_State* jit; + Table* h; /* to avoid collection/reuse strings */ +}; + +static void debug_message(void* context, const char* filename, long long line, const char* message) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; + ravi_writestring(ccontext->L, filename, strlen(filename)); + char temp[80]; + snprintf(temp, sizeof temp, "%lld: ", line); + ravi_writestring(ccontext->L, temp, strlen(temp)); + ravi_writestring(ccontext->L, message, strlen(message)); + ravi_writeline(ccontext->L); +} + +void error_message(void* context, const char* message) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; + ravi_writestring(ccontext->L, message, strlen(message)); + ravi_writeline(ccontext->L); +} + +/* Create a new proto and insert it into parent's list of protos */ +static Proto* lua_newProto(void* context, Proto* parent) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; + lua_State* L = ccontext->L; + Proto* p = luaF_newproto(L); + assert(parent); + /* FIXME make this more efficient */ + int old_size = parent->sizep; + int new_size = parent->sizep + 1; + luaM_reallocvector(L, parent->p, old_size, new_size, Proto*); + parent->p[old_size] = p; + parent->sizep++; + lua_assert(parent->sizep == new_size); + luaC_objbarrier(L, parent, p); + return p; +} + +/* + * Based off the Lua lexer code. Strings are anchored in a table initially - eventually + * ending up either in a Proto constant table or some other structure related to + * protos. + */ +TString* intern_string(lua_State* L, Table* h, const char* str, size_t l) { + TValue* o; /* entry for 'str' */ + TString* ts = luaS_newlstr(L, str, l); /* create new string */ + setsvalue2s(L, L->top++, ts); /* temporarily anchor it in stack */ + o = luaH_set(L, h, L->top - 1); + if (ttisnil(o)) { /* not in use yet? */ + /* boolean value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setbvalue(o, 1); /* t[string] = true */ + luaC_checkGC(L); + } + else { /* string already present */ + ts = tsvalue(keyfromval(o)); /* re-use value previously stored */ + } + L->top--; /* remove string from stack */ + return ts; +} + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use parser's table to cache position of constants in constant list +** and try to reuse constants. +*/ +static int add_konstant(lua_State* L, Proto* f, Table* h, TValue* key, TValue* v) { + TValue* idx = luaH_set(L, h, key); /* The k index is cached against the key */ + int k, oldsize, newsize; + if (ttisinteger(idx)) { /* is there an integer value index there? */ + k = cast_int(ivalue(idx)); + /* correct value? (warning: must distinguish floats from integers!) */ + if (k >= 0 && k < f->sizek && ttype(&f->k[k]) == ttype(v) && luaV_rawequalobj(&f->k[k], v)) + return k; /* reuse index */ + } + /* constant not found; create a new entry in Proto->k */ + oldsize = k = f->sizek; + newsize = oldsize + 1; + /* numerical value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setivalue(idx, k); + // FIXME make the allocation more efficient + luaM_reallocvector(L, f->k, oldsize, newsize, TValue); + setobj(L, &f->k[k], v); /* record the position k in the table against key */ + f->sizek++; + lua_assert(f->sizek == newsize); + luaC_barrier(L, f, v); + return k; +} + +/* +** Add a string to list of constants and return its index. +*/ +static int add_string_konstant(lua_State* L, Proto* p, Table* h, TString* s) { + TValue o; + setsvalue(L, &o, s); + return add_konstant(L, p, h, &o, &o); /* use string itself as key */ +} + +/* Create a Lua TString object from a string. Save it so that we can avoid creating same + * string again. + */ +static inline TString* create_luaString(lua_State* L, Table* h, struct string_object* s) { + if (s->userdata == NULL) { + /* Create and save it */ + s->userdata = intern_string(L, h, s->str, s->len); + } + return (TString*)s->userdata; +} + +/* Add a string constant to Proto and return its index */ +static int lua_newStringConstant(void* context, Proto* proto, struct string_object* s) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; + lua_State* L = ccontext->L; + Table* h = ccontext->h; + TString* ts = create_luaString(L, h, s); + return add_string_konstant(L, proto, h, ts); +} + +/* Add an upvalue. If the upvalue refers to a local variable in parent proto then idx should contain + * the register for the local variable and instack should be true, else idx should have the index of + * upvalue in parent proto and instack should be false. + */ +static int lua_addUpValue(void* context, Proto* f, struct string_object* name, unsigned idx, int instack, unsigned tc, + struct string_object* usertype) { + ravitype_t typecode = (ravitype_t)tc; + struct CompilerContext* ccontext = (struct CompilerContext*)context; + lua_State* L = ccontext->L; + Table* h = ccontext->h; + int oldsize = f->sizeupvalues; + int newsize = oldsize + 1; + int pos = oldsize; + // checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); + // FIXME optimize the allocation + luaM_reallocvector(L, f->upvalues, oldsize, newsize, Upvaldesc); + f->sizeupvalues++; + lua_assert(f->sizeupvalues == newsize); + f->upvalues[pos].instack = cast_byte(instack); /* is the upvalue in parent function's local stack ? */ + f->upvalues[pos].idx = cast_byte(idx); /* If instack then parent's local register else parent's upvalue index */ + TString* tsname = create_luaString(L, h, name); /* name of the variable */ + f->upvalues[pos].name = tsname; + f->upvalues[pos].ravi_type = typecode; + if (usertype != NULL) { + /* User type string goes into the proto's constant table */ + int kpos = lua_newStringConstant(context, f, usertype); + f->upvalues[pos].usertype = tsvalue(&f->k[kpos]); + } + else { + f->upvalues[pos].usertype = NULL; + } + luaC_objbarrier(L, f, tsname); + return pos; +} + +static void init_C_compiler(void* context) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; +#ifdef USE_MIRJIT + mir_prepare(ccontext->jit->jit, 2); +#endif +} +static void* compile_C(void* context, const char* C_src, unsigned len) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; + fprintf(stdout, "%s\n", C_src); +#ifdef USE_MIRJIT + return mir_compile_C_module(&ccontext->jit->options, ccontext->jit->jit, C_src, "input"); +#else + return NULL; +#endif +} +static void finish_C_compiler(void* context) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; +#ifdef USE_MIRJIT + mir_cleanup(ccontext->jit->jit); +#endif +} +static lua_CFunction get_compiled_function(void* context, void* module, const char* name) { + struct CompilerContext* ccontext = (struct CompilerContext*)context; +#if USE_MIRJIT + MIR_module_t M = (MIR_module_t)module; + return (lua_CFunction)mir_get_func(ccontext->jit->jit, M, name); +#else + return NULL; +#endif +} +static void lua_setProtoFunction(void* context, Proto* p, lua_CFunction func) { + p->ravi_jit.jit_function = func; + p->ravi_jit.jit_status = RAVI_JIT_COMPILED; +} +static void lua_setVarArg(void* context, Proto* p) { p->is_vararg = 1; } +static void lua_setNumParams(void* context, Proto* p, unsigned num_params) { p->numparams = cast_byte(num_params); } +static void lua_setMaxStackSize(void *context, Proto *p, unsigned max_stack_size) { + p->maxstacksize = cast_byte(max_stack_size); +} + +static int load_and_compile(lua_State* L) { + const char* s = luaL_checkstring(L, 1); + struct CompilerContext ccontext = {.L = L, .jit = G(L)->ravi_state}; + + LClosure* cl = luaF_newLclosure(L, 1); /* create main closure with 1 up-value for _ENV */ + setclLvalue(L, L->top, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); + ccontext.h = luaH_new(L); /* create table for string constants */ + sethvalue(L, L->top, ccontext.h); /* anchor it */ + luaD_inctop(L); + Proto* main_proto = cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); + + struct Ravi_CompilerInterface ravicomp_interface = {.source = s, + .source_len = strlen(s), + .source_name = "input", + .main_proto = main_proto, + .context = &ccontext, + .lua_newProto = lua_newProto, + .lua_newStringConstant = lua_newStringConstant, + .lua_addUpValue = lua_addUpValue, + .lua_setVarArg = lua_setVarArg, + .lua_setProtoFunction = lua_setProtoFunction, + .lua_setNumParams = lua_setNumParams, + .lua_setMaxStackSize = lua_setMaxStackSize, + .init_C_compiler = init_C_compiler, + .compile_C = compile_C, + .finish_C_compiler = finish_C_compiler, + .get_compiled_function = get_compiled_function, + .debug_message = debug_message, + .error_message = error_message}; + + int rc = raviX_compile(&ravicomp_interface); + L->top--; /* remove table for string constants */ + if (rc == 0) { + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luaF_initupvals(L, cl); + return 1; + } + else { + lua_error(L); + return 0; + } +} + +static const luaL_Reg ravilib[] = {{"load", load_and_compile}, {NULL, NULL}}; + +int(raviopen_compiler)(lua_State* L) { + luaL_newlib(L, ravilib); + return 1; +} \ No newline at end of file diff --git a/tests/comptests/00_ret.lua b/tests/comptests/00_ret.lua new file mode 100644 index 0000000..db8307f --- /dev/null +++ b/tests/comptests/00_ret.lua @@ -0,0 +1,10 @@ +f = compiler.load("return 1, 'hello', 5.6, true") +assert(f and type(f) == 'function') +local x = 4.3 +local a,b,c,d,e = f() +assert(a == 1) +assert(b == 'hello') +assert(c == 5.6) +assert(d == true) +assert(x == 4.3) +assert(e == nil) diff --git a/tests/comptests/01_mov.lua b/tests/comptests/01_mov.lua new file mode 100644 index 0000000..1008f5c --- /dev/null +++ b/tests/comptests/01_mov.lua @@ -0,0 +1,8 @@ +f = compiler.load("local a, b, c = 4.2, true, 'hi' return a, b, c") +assert(f and type(f) == 'function') +local z = 62 +local a,b,c,d = f() +assert(z == 62) +assert(a == 4.2) +assert(b == true) +assert(c == 'hi') \ No newline at end of file diff --git a/tests/comptests/02_cbr_br.lua b/tests/comptests/02_cbr_br.lua new file mode 100644 index 0000000..93fee49 --- /dev/null +++ b/tests/comptests/02_cbr_br.lua @@ -0,0 +1,3 @@ +f = compiler.load("local a,b = 1,2 return a or b"); +assert(f and type(f) == 'function') +assert(f() == 1) \ No newline at end of file diff --git a/tests/comptests/03_cbr_br.lua b/tests/comptests/03_cbr_br.lua new file mode 100644 index 0000000..10085e2 --- /dev/null +++ b/tests/comptests/03_cbr_br.lua @@ -0,0 +1,3 @@ +f = compiler.load("local a,b = 1,2 return a and b"); +assert(f and type(f) == 'function') +assert(f() == 2) \ No newline at end of file diff --git a/tests/comptests/04_cbr_br.lua b/tests/comptests/04_cbr_br.lua new file mode 100644 index 0000000..59fbb13 --- /dev/null +++ b/tests/comptests/04_cbr_br.lua @@ -0,0 +1,3 @@ +f = compiler.load("local a = 2 return 1 or a"); +assert(f and type(f) == 'function') +assert(f() == 1) \ No newline at end of file diff --git a/tests/comptests/05_cbr_br.lua b/tests/comptests/05_cbr_br.lua new file mode 100644 index 0000000..e237500 --- /dev/null +++ b/tests/comptests/05_cbr_br.lua @@ -0,0 +1,3 @@ +f = compiler.load("local a = 2 return 1 and a"); +assert(f and type(f) == 'function') +assert(f() == 2) \ No newline at end of file