#include "ScriptEngine.hpp" #include struct LuaJITEngine : ScriptEngine { lua_State* L = NULL; // This is a mirror of ProcessBlock that we are going to use // to provide 1-based indices within the Lua VM struct LuaProcessBlock { float sampleRate; float sampleTime; int bufferSize; float* inputs[NUM_ROWS + 1]; float* outputs[NUM_ROWS + 1]; float* knobs; bool* switches; float* lights[NUM_ROWS + 1]; float* switchLights[NUM_ROWS + 1]; }; LuaProcessBlock luaBlock; ~LuaJITEngine() { if (L) lua_close(L); } std::string getEngineName() override { return "Lua"; } int run(const std::string& path, const std::string& script) override { ProcessBlock* block = getProcessBlock(); // Initialize all the pointers with an offset of -1 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" luaBlock.knobs = &block->knobs[-1]; luaBlock.switches = &block->switches[-1]; for (int i = 0; i < NUM_ROWS; i++) { luaBlock.inputs[i + 1] = &block->inputs[i][-1]; luaBlock.outputs[i + 1] = &block->outputs[i][-1]; luaBlock.lights[i + 1] = &block->lights[i][-1]; luaBlock.switchLights[i + 1] = &block->switchLights[i][-1]; } #pragma GCC diagnostic pop L = luaL_newstate(); if (!L) { display("Could not create LuaJIT context"); return -1; } // Import a subset of the standard library static const luaL_Reg lj_lib_load[] = { {"", luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, {LUA_TABLIBNAME, luaopen_table}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {LUA_BITLIBNAME, luaopen_bit}, {LUA_JITLIBNAME, luaopen_jit}, {LUA_FFILIBNAME, luaopen_ffi}, {NULL, NULL} }; for (const luaL_Reg* lib = lj_lib_load; lib->func; lib++) { lua_pushcfunction(L, lib->func); lua_pushstring(L, lib->name); lua_call(L, 1, 0); } // Set user pointer lua_pushlightuserdata(L, this); lua_setglobal(L, "_engine"); // Set global functions // lua_pushcfunction(L, native_print); // lua_setglobal(L, "print"); lua_pushcfunction(L, native_display); lua_setglobal(L, "display"); // Set config lua_newtable(L); { // frameDivider lua_pushinteger(L, 32); lua_setfield(L, -2, "frameDivider"); // bufferSize lua_pushinteger(L, 1); lua_setfield(L, -2, "bufferSize"); } lua_setglobal(L, "config"); // Load the FFI auxiliary functions. std::stringstream ffi_stream; ffi_stream << "local ffi = require('ffi')" << std::endl // Describe the struct `LuaProcessBlock` so that LuaJIT knows how to access the data << "ffi.cdef[[" << std::endl << "struct LuaProcessBlock {" << std::endl << "float sampleRate;" << std::endl << "float sampleTime;" << std::endl << "int bufferSize;" << std::endl << "float *inputs[" << NUM_ROWS + 1 << "];" << std::endl << "float *outputs[" << NUM_ROWS + 1 << "];" << std::endl << "float *knobs;" << std::endl << "bool *switches;" << std::endl << "float *lights[" << NUM_ROWS + 1 << "];" << std::endl << "float *switchLights[" << NUM_ROWS + 1 << "];" << std::endl << "};]]" << std::endl // Declare the function `_castBlock` used to transform `luaBlock` pointer into a LuaJIT cdata << "_ffi_cast = ffi.cast" << std::endl << "function _castBlock(b) return _ffi_cast('struct LuaProcessBlock*', b) end" << std::endl // Remove global functions that could be abused << "jit = nil; require = nil; ffi = nil; load = nil; loadfile = nil; loadstring = nil; dofile = nil;" << std::endl; std::string ffi_script = ffi_stream.str(); // Compile the ffi script if (luaL_loadbuffer(L, ffi_script.c_str(), ffi_script.size(), "ffi_script.lua")) { const char* s = lua_tostring(L, -1); WARN("LuaJIT: %s", s); display(s); lua_pop(L, 1); return -1; } // Run the ffi script if (lua_pcall(L, 0, 0, 0)) { const char* s = lua_tostring(L, -1); WARN("LuaJIT: %s", s); display(s); lua_pop(L, 1); return -1; } // Compile user script if (luaL_loadbuffer(L, script.c_str(), script.size(), path.c_str())) { const char* s = lua_tostring(L, -1); WARN("LuaJIT: %s", s); display(s); lua_pop(L, 1); return -1; } // Run script if (lua_pcall(L, 0, 0, 0)) { const char* s = lua_tostring(L, -1); WARN("LuaJIT: %s", s); display(s); lua_pop(L, 1); return -1; } // Get config lua_getglobal(L, "config"); { // frameDivider lua_getfield(L, -1, "frameDivider"); int frameDivider = lua_tointeger(L, -1); setFrameDivider(frameDivider); lua_pop(L, 1); // bufferSize lua_getfield(L, -1, "bufferSize"); int bufferSize = lua_tointeger(L, -1); setBufferSize(bufferSize); lua_pop(L, 1); } lua_pop(L, 1); // Get process function lua_getglobal(L, "process"); if (!lua_isfunction(L, -1)) { display("No process() function"); return -1; } // Create block object lua_getglobal(L, "_castBlock"); lua_pushlightuserdata(L, (void*) &luaBlock); if (lua_pcall(L, 1, 1, 0)) { const char* s = lua_tostring(L, -1); WARN("LuaJIT: Error casting block: %s", s); display(s); lua_pop(L, 1); return -1; } return 0; } int process() override { ProcessBlock* block = getProcessBlock(); // Update the values of the block. // The pointer values do not change. luaBlock.sampleRate = block->sampleRate; luaBlock.sampleTime = block->sampleTime; luaBlock.bufferSize = block->bufferSize; // Duplicate process function lua_pushvalue(L, -2); // Duplicate block lua_pushvalue(L, -2); // Call process function if (lua_pcall(L, 1, 0, 0)) { const char* err = lua_tostring(L, -1); WARN("LuaJIT: %s", err); display(err); return -1; } return 0; } static LuaJITEngine* getEngine(lua_State* L) { lua_getglobal(L, "_engine"); LuaJITEngine* engine = (LuaJITEngine*) lua_touserdata(L, -1); lua_pop(L, 1); return engine; } // static int native_print(lua_State* L) { // lua_getglobal(L, "tostring"); // lua_pushvalue(L, 1); // lua_call(L, 1, 1); // const char* s = lua_tostring(L, 1); // INFO("LuaJIT: %s", s); // return 0; // } static int native_display(lua_State* L) { lua_getglobal(L, "tostring"); lua_pushvalue(L, 1); lua_call(L, 1, 1); const char* s = lua_tostring(L, -1); if (!s) s = "(null)"; getEngine(L)->display(s); return 0; } }; __attribute__((constructor(1000))) static void constructor() { addScriptEngine("lua"); }