diff --git a/.gitmodules b/.gitmodules index 93b647f893..598e339101 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,3 +24,7 @@ [submodule "firmware/ext/uzlib"] path = firmware/ext/uzlib url = https://github.com/pfalcon/uzlib +[submodule "firmware/ext/lua"] + path = firmware/ext/lua + url = https://github.com/rusefi/lua + branch = rusefi-5.4.3 diff --git a/firmware/bootloader/src/Makefile b/firmware/bootloader/src/Makefile index 2dc32e4e5b..8d6d9e5404 100644 --- a/firmware/bootloader/src/Makefile +++ b/firmware/bootloader/src/Makefile @@ -310,7 +310,7 @@ UINCDIR = ULIBDIR = # List all user libraries here -ULIBS = -lm +ULIBS = -lm --specs=nano.specs # # End of user defines diff --git a/firmware/config/stm32f4ems/efifeatures.h b/firmware/config/stm32f4ems/efifeatures.h index fa1fb5fb97..a212003193 100644 --- a/firmware/config/stm32f4ems/efifeatures.h +++ b/firmware/config/stm32f4ems/efifeatures.h @@ -44,6 +44,7 @@ #define SC_BUFFER_SIZE 4000 #endif +#define EFI_LUA FALSE /** * if you have a 60-2 trigger, or if you just want better performance, you diff --git a/firmware/config/stm32h7ems/efifeatures.h b/firmware/config/stm32h7ems/efifeatures.h index c9ccedda62..51e423fd2b 100644 --- a/firmware/config/stm32h7ems/efifeatures.h +++ b/firmware/config/stm32h7ems/efifeatures.h @@ -31,3 +31,6 @@ // H7 has dual bank, so flash on its own (low priority) thread so as to not block any other operations #define EFI_FLASH_WRITE_THREAD TRUE + +#undef EFI_LUA +#define EFI_LUA TRUE diff --git a/firmware/controllers/lua/lua.cpp b/firmware/controllers/lua/lua.cpp new file mode 100644 index 0000000000..91092cb3c7 --- /dev/null +++ b/firmware/controllers/lua/lua.cpp @@ -0,0 +1,247 @@ + +#include "rusefi_lua.h" +#include "thread_controller.h" +#include "perf_trace.h" + +#if EFI_LUA + +#include "lua.hpp" +#include "lua_hooks.h" + +#if EFI_PROD_CODE +#include "ch.h" + +#define LUA_HEAP_SIZE 20000 + +static memory_heap_t heap; + +static void* myAlloc(void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) { + if (nsize == 0) { + // requested size is zero, free if necessary and return nullptr + if (ptr) { + chHeapFree(ptr); + } + + return nullptr; + } + + void *new_mem = chHeapAlloc(&heap, nsize); + + if (!ptr) { + // No old pointer passed in, simply return allocated block + return new_mem; + } + + // An old pointer was passed in, copy the old data in, then free + if (new_mem != nullptr) { + memcpy(new_mem, ptr, chHeapGetSize(ptr) > nsize ? nsize : chHeapGetSize(ptr)); + chHeapFree(ptr); + } + + return new_mem; +} +#else // not EFI_PROD_CODE +// Non-MCU code can use plain realloc function instead of custom implementation +static void* myAlloc(void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) { + return realloc(ptr, nsize); +} +#endif // EFI_PROD_CODE + +static lua_State* setupLuaState() { + auto *ls = lua_newstate(myAlloc, NULL); + + // TODO handle null ls + if (!ls) { + firmwareError(OBD_PCM_Processor_Fault, "Failed to start Lua interpreter"); + + return nullptr; + } + + // load libraries + luaopen_base(ls); + luaopen_math(ls); + + // Load rusEFI hooks + configureRusefiLuaHooks(ls); + + // run a GC cycle + lua_gc(ls, LUA_GCCOLLECT, 0); + + // set GC settings + // see https://www.lua.org/manual/5.4/manual.html#2.5.1 + lua_gc(ls, LUA_GCINC, 50, 1000, 9); + + return ls; +} + +void stopLua(lua_State* ls) { + lua_close(ls); +} + +static bool loadScript(lua_State* ls, const char* scriptStr) { + efiPrintf("loading script length: %d", efiStrlen(scriptStr)); + + if (0 != luaL_dostring(ls, scriptStr)) { + efiPrintf("LUA error loading script: %s", lua_tostring(ls, -1)); + lua_pop(ls, 1); + return false; + } + + efiPrintf("script loaded"); + + return true; +} + +#if !EFI_UNIT_TEST +struct LuaThread : ThreadController<4096> { + LuaThread() : ThreadController("lua", PRIO_LUA) { } + + void ThreadTask() override; +}; + +void LuaThread::ThreadTask() { + void* buf = malloc(LUA_HEAP_SIZE); + chHeapObjectInit(&heap, buf, LUA_HEAP_SIZE); + + auto ls = setupLuaState(); + + // couldn't start Lua interpreter, bail out + if (!ls) { + return; + } + + //auto scriptStr = "function onTick()\nlocal rpm = getSensor(3)\nif rpm ~= nil then\nprint('RPM: ' ..rpm)\nend\nend\n"; + auto scriptStr = "n=0\nfunction onTick()\nprint('hello lua ' ..n)\nn=n+1\nend\n"; + + loadScript(ls, scriptStr); + + while (!chThdShouldTerminateX()) { + // run the tick function + lua_getglobal(ls, "onTick"); + if (lua_isnil(ls, -1)) { + // TODO: handle missing tick function + lua_pop(ls, 1); + lua_settop(ls, 0); + continue; + } + + { + ScopePerf perf(PE::LuaTickFunction); + + int status = lua_pcall(ls, 0, 0, 0); + + if (0 != status) { + // error calling hook function + auto errMsg = lua_tostring(ls, -1); + efiPrintf("lua err %s", errMsg); + lua_pop(ls, 1); + } + } + + lua_settop(ls, 0); + } +} + +static LuaThread luaThread; + +void startLua() { + luaThread.Start(); +} + +#else // not EFI_UNIT_TEST + +void startLua() { + // todo +} + +#include +#include + +static lua_State* runScript(const char* script) { + auto ls = setupLuaState(); + + if (!ls) { + throw new std::logic_error("Call to setupLuaState failed, returned null"); + } + + if (!loadScript(ls, script)) { + lua_close(ls); + throw new std::logic_error("Call to loadScript failed"); + } + + lua_getglobal(ls, "testFunc"); + if (lua_isnil(ls, -1)) { + lua_close(ls); + throw new std::logic_error("Failed to find function testFunc"); + } + + int status = lua_pcall(ls, 0, 1, 0); + + if (0 != status) { + std::string msg = std::string("lua error while running script: ") + lua_tostring(ls, -1); + lua_close(ls); + throw new std::logic_error(msg); + } + + return ls; +} + +expected testLuaReturnsNumberOrNil(const char* script) { + auto ls = runScript(script); + + // check nil return first + if (lua_isnil(ls, -1)) { + lua_close(ls); + return unexpected; + } + + // If not nil, it should be a number + if (!lua_isnumber(ls, -1)) { + lua_close(ls); + throw new std::logic_error("Returned value is not a number"); + } + + // pop the return value + float retVal = lua_tonumber(ls, -1); + + lua_close(ls); + + return retVal; +} + +float testLuaReturnsNumber(const char* script) { + auto ls = runScript(script); + + // check the return value + if (!lua_isnumber(ls, -1)) { + lua_close(ls); + throw new std::logic_error("Returned value is not a number"); + } + + // pop the return value + float retVal = lua_tonumber(ls, -1); + + lua_close(ls); + + return retVal; +} + +int testLuaReturnsInteger(const char* script) { + auto ls = runScript(script); + + // pop the return value; + if (!lua_isinteger(ls, -1)) { + lua_close(ls); + throw new std::logic_error("Returned value is not an integer"); + } + + int retVal = lua_tointeger(ls, -1); + + lua_close(ls); + + return retVal; +} + +#endif // EFI_UNIT_TEST + +#endif // EFI_LUA diff --git a/firmware/controllers/lua/lua.hpp b/firmware/controllers/lua/lua.hpp new file mode 100644 index 0000000000..ec417f5946 --- /dev/null +++ b/firmware/controllers/lua/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/firmware/controllers/lua/lua.mk b/firmware/controllers/lua/lua.mk index b3b187fe4c..67278e2a0e 100644 --- a/firmware/controllers/lua/lua.mk +++ b/firmware/controllers/lua/lua.mk @@ -1,36 +1,40 @@ LUA_DIR=$(CONTROLLERS_DIR)/lua LUA_EXT=$(PROJECT_DIR)/ext/lua -# ALLCPPSRC += $(LUA_DIR)/lua.cpp -ALLINC += $(LUA_DIR) # $(LUA_EXT)/src $(LUA_EXT)/etc -# ALLCSRC += $(LUA_EXT)/src/lapi.c \ -# $(LUA_EXT)/src/lcode.c \ -# $(LUA_EXT)/src/ldebug.c \ -# $(LUA_EXT)/src/ldo.c \ -# $(LUA_EXT)/src/ldump.c \ -# $(LUA_EXT)/src/lfunc.c \ -# $(LUA_EXT)/src/lgc.c \ -# $(LUA_EXT)/src/llex.c \ -# $(LUA_EXT)/src/lmem.c \ -# $(LUA_EXT)/src/lobject.c \ -# $(LUA_EXT)/src/lopcodes.c \ -# $(LUA_EXT)/src/lparser.c \ -# $(LUA_EXT)/src/lstate.c \ -# $(LUA_EXT)/src/lstring.c \ -# $(LUA_EXT)/src/ltable.c \ -# $(LUA_EXT)/src/ltm.c \ -# $(LUA_EXT)/src/lundump.c \ -# $(LUA_EXT)/src/lvm.c \ -# $(LUA_EXT)/src/lzio.c \ -# $(LUA_EXT)/src/lrotable.c \ -# $(LUA_EXT)/src/lauxlib.c \ -# $(LUA_EXT)/src/lbaselib.c \ -# $(LUA_EXT)/src/ldblib.c \ -# $(LUA_EXT)/src/lmathlib.c \ -# $(LUA_EXT)/src/loslib.c \ -# $(LUA_EXT)/src/ltablib.c \ -# $(LUA_EXT)/src/lstrlib.c \ -# $(LUA_EXT)/src/loadlib.c \ -# $(LUA_EXT)/src/linit.c \ -# $(LUA_EXT)/src/bit.c \ -# $(LUA_EXT)/src/modp_numtoa.c \ +ALLCPPSRC += $(LUA_DIR)/lua.cpp \ + $(LUA_DIR)/lua_hooks.cpp \ + +ALLINC += $(LUA_DIR) $(LUA_EXT) +ALLCSRC += \ + $(LUA_EXT)/lapi.c \ + $(LUA_EXT)/lcode.c \ + $(LUA_EXT)/lctype.c \ + $(LUA_EXT)/ldebug.c \ + $(LUA_EXT)/ldo.c \ + $(LUA_EXT)/ldump.c \ + $(LUA_EXT)/lfunc.c \ + $(LUA_EXT)/lgc.c \ + $(LUA_EXT)/llex.c \ + $(LUA_EXT)/lmem.c \ + $(LUA_EXT)/lobject.c \ + $(LUA_EXT)/lopcodes.c \ + $(LUA_EXT)/lparser.c \ + $(LUA_EXT)/lstate.c \ + $(LUA_EXT)/lstring.c \ + $(LUA_EXT)/ltable.c \ + $(LUA_EXT)/ltm.c \ + $(LUA_EXT)/lundump.c \ + $(LUA_EXT)/lvm.c \ + $(LUA_EXT)/lzio.c \ + $(LUA_EXT)/lauxlib.c \ + $(LUA_EXT)/lbaselib.c \ + $(LUA_EXT)/lcorolib.c \ + $(LUA_EXT)/ldblib.c \ + $(LUA_EXT)/liolib.c \ + $(LUA_EXT)/lmathlib.c \ + $(LUA_EXT)/loadlib.c \ + $(LUA_EXT)/loslib.c \ + $(LUA_EXT)/lstrlib.c \ + $(LUA_EXT)/ltablib.c \ + $(LUA_EXT)/lutf8lib.c \ + $(LUA_EXT)/linit.c \ diff --git a/firmware/controllers/lua/lua_hooks.cpp b/firmware/controllers/lua/lua_hooks.cpp new file mode 100644 index 0000000000..8969448ef5 --- /dev/null +++ b/firmware/controllers/lua/lua_hooks.cpp @@ -0,0 +1,34 @@ +#include "lua.hpp" +#include "lua_hooks.h" + +#include "loggingcentral.h" +#include "sensor.h" + +static int lua_efi_print(lua_State* l) { + auto msg = luaL_checkstring(l, 1); + + efiPrintf("LUA: %s", msg); + + return 0; +} + +static int lua_get_sensor(lua_State* l) { + auto sensorIndex = luaL_checkinteger(l, 1); + + auto result = Sensor::get(static_cast(sensorIndex)); + + if (result) { + // return value if valid + lua_pushnumber(l, result.Value); + } else { + // return nil if invalid + lua_pushnil(l); + } + + return 1; +} + +void configureRusefiLuaHooks(lua_State* l) { + lua_register(l, "print", lua_efi_print); + lua_register(l, "getSensor", lua_get_sensor); +} diff --git a/firmware/controllers/lua/lua_hooks.h b/firmware/controllers/lua/lua_hooks.h new file mode 100644 index 0000000000..b7e225b96a --- /dev/null +++ b/firmware/controllers/lua/lua_hooks.h @@ -0,0 +1,3 @@ +#pragma once + +void configureRusefiLuaHooks(lua_State*); diff --git a/firmware/controllers/lua/luaconf.h b/firmware/controllers/lua/luaconf.h new file mode 100644 index 0000000000..a6c1865d42 --- /dev/null +++ b/firmware/controllers/lua/luaconf.h @@ -0,0 +1,577 @@ +/* +** $Id: luaconf.h $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** General Configuration File for Lua +** +** Some definitions here can be changed externally, through the compiler +** (e.g., with '-D' options): They are commented out or protected +** by '#if !defined' guards. However, several other definitions +** should be changed directly here, either because they affect the +** Lua ABI (by making the changes here, you ensure that all software +** connected to Lua, such as C libraries, will be compiled with the same +** configuration); or because they are seldom changed. +** +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + +/* +@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits. +*/ +#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3) + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +*/ +#define LUA_32BITS 1 + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ + +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll" +#endif + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" +#endif + +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if !defined(LUA_DIRSEP) + +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* +** More often than not the libs go together with the core. +*/ +#define LUALIB_API LUA_API +#define LUAMOD_API LUA_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, +** none of which to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("internal"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC(dec) LUAI_FUNC dec +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_3) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +** (These functions were already officially removed in 5.3; +** nevertheless they are still available here.) +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +** (These macros were also officially removed in 5.3, but they are still +** available here.) +*/ +#define LUA_COMPAT_APIINTCASTS + + +/* +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. +*/ +#define LUA_COMPAT_LT_LE + + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +** (Once more, these macros were officially removed in 5.3, but they are +** still available here.) +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +#endif /* } */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers (low-level part). +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_floatatt(x) corrects float attribute 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeral to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number with an integral value +** to an integer, or returns 0 if float is not within the range of +** a lua_Integer. (The range comparisons are tricky because of +** rounding. The tests here assume a two-complement representation, +** where MININTEGER always has an exact representation as a float; +** MAXINTEGER may not have one, and therefore its conversion to float +** may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#define LUA_NUMBER float + +#define l_floatatt(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7f" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +/* +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a LUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ LUA_MAXUNSIGNED is the maximum value for a LUA_UNSIGNED. +@@ LUA_UNSIGNEDBITS is the number of bits in a LUA_UNSIGNED. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +#define LUA_UNSIGNEDBITS (sizeof(LUA_UNSIGNED) * CHAR_BIT) + + +/* now the variable definitions */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#define LUA_MAXUNSIGNED UINT_MAX + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if EFI_UNIT_TEST + // Unit tests use normal snprintf + #define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else + // Real FW uses ChibiOS chsnprintf implementation + #include + #include + #include "chprintf.h" + #define l_sprintf(s,sz,f,i) chsnprintf(s,sz,f,i) +#endif + +/* +@@ lua_strx2number converts a hexadecimal numeral to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to a hexadecimal numeral. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include the header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + + +/* +** macros to improve jump prediction, used mostly for error handling +** and debug facilities. (Some macros in the Lua API use these macros. +** Define LUA_NOBUILTIN if you do not want '__builtin_expect' in your +** code.) +*/ +#if !defined(luai_likely) + +#if defined(__GNUC__) && !defined(LUA_NOBUILTIN) +#define luai_likely(x) (__builtin_expect(((x) != 0), 1)) +#define luai_unlikely(x) (__builtin_expect(((x) != 0), 0)) +#else +#define luai_likely(x) (x) +#define luai_unlikely(x) (x) +#endif + +#endif + + +#if defined(LUA_CORE) || defined(LUA_LIB) +/* shorter names for Lua's own use */ +#define l_likely(x) luai_likely(x) +#define l_unlikely(x) luai_unlikely(x) +#endif + + + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +** (It must fit into max(size_t)/32.) +*/ +#if LUAI_IS32INT +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +*/ +#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number))) + + +/* +@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure +** maximum alignment for the other items in that union. +*/ +#define LUAI_MAXALIGN lua_Number n; double u; void *s; lua_Integer i; long l + +/* }================================================================== */ + + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/firmware/controllers/lua/rusefi_lua.h b/firmware/controllers/lua/rusefi_lua.h index f4a1c07e9e..b219aa9553 100644 --- a/firmware/controllers/lua/rusefi_lua.h +++ b/firmware/controllers/lua/rusefi_lua.h @@ -3,3 +3,11 @@ #pragma once void startLua(); + +#if EFI_UNIT_TEST +#include "expected.h" + +expected testLuaReturnsNumberOrNil(const char* script); +float testLuaReturnsNumber(const char* script); +int testLuaReturnsInteger(const char* script); +#endif diff --git a/firmware/development/perf_trace.h b/firmware/development/perf_trace.h index 79e4a3a6b1..323ef8de15 100644 --- a/firmware/development/perf_trace.h +++ b/firmware/development/perf_trace.h @@ -64,6 +64,7 @@ enum class PE : uint8_t { GlobalUnlock, SoftwareKnockProcess, LogTriggerTooth, + LuaTickFunction, // enum_end_tag // The tag above is consumed by PerfTraceTool.java // please note that the tool requires a comma at the end of last value diff --git a/firmware/ext/lua b/firmware/ext/lua new file mode 160000 index 0000000000..6e87cbec32 --- /dev/null +++ b/firmware/ext/lua @@ -0,0 +1 @@ +Subproject commit 6e87cbec322073039e74681e7e10ad079a47c3f4 diff --git a/firmware/hw_layer/ports/stm32/stm32h7/cfg/chconf.h b/firmware/hw_layer/ports/stm32/stm32h7/cfg/chconf.h index b2077a2ac1..ff92626020 100644 --- a/firmware/hw_layer/ports/stm32/stm32h7/cfg/chconf.h +++ b/firmware/hw_layer/ports/stm32/stm32h7/cfg/chconf.h @@ -340,7 +340,7 @@ * @note The default is @p TRUE. */ #if !defined(CH_CFG_USE_MEMCORE) -#define CH_CFG_USE_MEMCORE FALSE +#define CH_CFG_USE_MEMCORE TRUE #endif /** @@ -369,7 +369,7 @@ * @note Mutexes are recommended. */ #if !defined(CH_CFG_USE_HEAP) -#define CH_CFG_USE_HEAP FALSE +#define CH_CFG_USE_HEAP TRUE #endif /** diff --git a/firmware/hw_layer/rtc_helper.cpp b/firmware/hw_layer/rtc_helper.cpp index 309039f3cf..d8fd6ca276 100644 --- a/firmware/hw_layer/rtc_helper.cpp +++ b/firmware/hw_layer/rtc_helper.cpp @@ -12,6 +12,7 @@ #include "os_access.h" #include "os_util.h" #include "rtc_helper.h" +#include #if EFI_RTC static RTCDateTime timespec; @@ -35,6 +36,13 @@ void date_get_tm(struct tm *timp) { #endif /* EFI_RTC */ } +#if EFI_PROD_CODE +// Lua needs this function, but we don't necessarily have to implement it +extern "C" int _gettimeofday(timeval* tv, void* tzvp) { + return 0; +} +#endif + static time_t GetTimeUnixSec(void) { #if EFI_RTC struct tm tim; diff --git a/firmware/util/datalogging.h b/firmware/util/datalogging.h index 069fac5465..d3ba876156 100644 --- a/firmware/util/datalogging.h +++ b/firmware/util/datalogging.h @@ -12,8 +12,6 @@ #include #include -#define DELIMETER "," - // todo: migrate to external buffer so that different instances have different // size of buffers? class Logging { diff --git a/firmware/util/loggingcentral.h b/firmware/util/loggingcentral.h index c04b41ac9a..1b04e7c29a 100644 --- a/firmware/util/loggingcentral.h +++ b/firmware/util/loggingcentral.h @@ -7,6 +7,9 @@ #pragma once #include +#include "rusefi_generated.h" + +#define DELIMETER "," class Logging; diff --git a/unit_tests/Makefile b/unit_tests/Makefile index 64e81babd9..7bd793c9dc 100644 --- a/unit_tests/Makefile +++ b/unit_tests/Makefile @@ -113,7 +113,8 @@ include tests/tests.mk # C sources that can be compiled in ARM or THUMB mode depending on the global # setting. -CSRC = $(UTILSRC) \ +CSRC = $(ALLCSRC) \ + $(UTILSRC) \ $(CONTROLLERS_ALGO_SRC) \ $(CONTROLLERS_CORE_SRC) \ $(CONTROLLERS_MATH_SRC) \ @@ -124,7 +125,8 @@ CSRC = $(UTILSRC) \ # C++ sources that can be compiled in ARM or THUMB mode depending on the global # setting. -CPPSRC = $(UTILSRC_CPP) \ +CPPSRC = $(ALLCPPSRC) \ + $(UTILSRC_CPP) \ gtest-all.cpp \ gmock-all.cpp \ $(CONTROLLERS_ALGO_SRC_CPP) \ @@ -174,6 +176,7 @@ ASMSRC = $(PORTASM) INCDIR = . \ + $(ALLINC) \ $(UTIL_INC) \ $(PROJECT_DIR)/config/engines \ $(CONTROLLERS_INC) \ diff --git a/unit_tests/efifeatures.h b/unit_tests/efifeatures.h index 5b7c8ba6c9..2e2e955180 100644 --- a/unit_tests/efifeatures.h +++ b/unit_tests/efifeatures.h @@ -70,3 +70,5 @@ #define EFI_MAP_AVERAGING TRUE #define EFI_FUEL_PUMP TRUE + +#define EFI_LUA TRUE diff --git a/unit_tests/tests/lua/test_lua_basic.cpp b/unit_tests/tests/lua/test_lua_basic.cpp new file mode 100644 index 0000000000..5b108ba9b0 --- /dev/null +++ b/unit_tests/tests/lua/test_lua_basic.cpp @@ -0,0 +1,78 @@ +#include "rusefi_lua.h" +#include + +TEST(LuaBasic, ReturnsNumber) { + auto script = R"( + function testFunc() + return 5.5 + end + )"; + + float result = testLuaReturnsNumber(script); + + EXPECT_FLOAT_EQ(result, 5.5f); +} + +TEST(LuaBasic, ReturnsInteger) { + auto script = R"( + function testFunc() + return 33 + end + )"; + + int result = testLuaReturnsInteger(script); + + EXPECT_EQ(result, 33); +} + +TEST(LuaBasic, InvalidScript) { + EXPECT_ANY_THROW(testLuaReturnsInteger("ggg")); +} + +TEST(LuaBasic, NoFunction) { + EXPECT_ANY_THROW(testLuaReturnsInteger(R"( + function testFuncccccc() + return 3 + end + )")); +} + +TEST(LuaBasic, WrongReturnTypeExpectIntegerReturnsNothing) { + EXPECT_ANY_THROW(testLuaReturnsInteger(R"( + function testFunc() + end + )")); +} + +TEST(LuaBasic, WrongReturnTypeExpectIntegerReturnsNil) { + EXPECT_ANY_THROW(testLuaReturnsInteger(R"( + function testFunc() + return nil + end + )")); +} + +TEST(LuaBasic, ExpectNumOrNilReturnsNil) { + EXPECT_EQ(testLuaReturnsNumberOrNil(R"( + function testFunc() + return nil + end + )"), unexpected); +} + +TEST(LuaBasic, ExpectNumOrNilReturnsNumber) { + EXPECT_FLOAT_EQ(56.3f, testLuaReturnsNumberOrNil(R"( + function testFunc() + return 56.3 + end + )").value_or(0)); +} + +TEST(LuaBasic, ExpectNumOrNilReturnsNothing) { + // Returning nothing is generally functionally equivalent to returning nil + EXPECT_EQ(testLuaReturnsNumberOrNil(R"( + function testFunc() + return + end + )"), unexpected); +} diff --git a/unit_tests/tests/lua/test_lua_hooks.cpp b/unit_tests/tests/lua/test_lua_hooks.cpp new file mode 100644 index 0000000000..c991884b61 --- /dev/null +++ b/unit_tests/tests/lua/test_lua_hooks.cpp @@ -0,0 +1,21 @@ +#include "rusefi_lua.h" +#include +#include "sensor.h" + +static const char* getSensorTest = R"( + +function testFunc() + return getSensor(10) +end + +)"; + +TEST(LuaHooks, TestGetSensor) { + // Test failed sensor, returns nil + Sensor::resetMockValue(static_cast(10)); + EXPECT_EQ(testLuaReturnsNumberOrNil(getSensorTest), unexpected); + + // Now test with a value, returns value + Sensor::setMockValue(10, 33); + EXPECT_EQ(testLuaReturnsNumberOrNil(getSensorTest).value_or(0), 33); +} diff --git a/unit_tests/tests/tests.mk b/unit_tests/tests/tests.mk index b81f162bab..4547168adf 100644 --- a/unit_tests/tests/tests.mk +++ b/unit_tests/tests/tests.mk @@ -15,6 +15,8 @@ TESTS_SRC_CPP = \ tests/ignition_injection/test_fuelCut.cpp \ tests/ignition_injection/test_fuel_computer.cpp \ tests/ignition_injection/test_injector_model.cpp \ + tests/lua/test_lua_basic.cpp \ + tests/lua/test_lua_hooks.cpp \ tests/sensor/test_cj125.cpp \ tests/util/test_buffered_writer.cpp \ tests/util/test_error_accumulator.cpp \