diff --git a/CMakeLists.txt b/CMakeLists.txt index 5daa78976d9..fb62fe03900 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -768,7 +768,9 @@ endif() if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING) find_feature(USE_JSON_C "json-c") - if(NOT USE_LUA VERSION_LESS 5.1) + if(USE_LUA STREQUAL "JIT") + find_feature(USE_LUA "LuaJIT") + elseif(NOT USE_LUA VERSION_LESS 5.1) find_feature(USE_LUA "Lua" ${USE_LUA}) else() find_feature(USE_LUA "Lua") @@ -799,10 +801,17 @@ if(ENABLE_SCRIPTING) endif() if(USE_LUA) list(APPEND FEATURE_DEFINES USE_LUA) - include_directories(AFTER ${LUA_INCLUDE_DIR}) - list(APPEND FEATURE_DEFINES LUA_VERSION_ONLY=\"${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}\") - list(APPEND DEPENDENCY_LIB ${LUA_LIBRARY}) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},liblua${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}-0") + if(USE_LUA STREQUAL "JIT") + include_directories(AFTER ${LUAJIT_INCLUDE_DIR}) + list(APPEND FEATURE_DEFINES LUA_VERSION_ONLY=\"${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR}\") + list(APPEND DEPENDENCY_LIB ${LUAJIT_LIBRARY}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libluajit${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}-0") + else() + include_directories(AFTER ${LUA_INCLUDE_DIR}) + list(APPEND FEATURE_DEFINES LUA_VERSION_ONLY=\"${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}\") + list(APPEND DEPENDENCY_LIB ${LUA_LIBRARY}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},liblua${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}-0") + endif() endif() if(BUILD_PYTHON) @@ -1308,7 +1317,9 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) message(STATUS " OpenGL support: ${SUMMARY_GL}") message(STATUS "Scripting support: ${ENABLE_SCRIPTING}") if(ENABLE_SCRIPTING) - if(LUA_VERSION_STRING) + if(LUAJIT_VERSION_STRING) + message(STATUS " Lua: JIT ${LUAJIT_VERSION_STRING}") + elseif(LUA_VERSION_STRING) message(STATUS " Lua: ${LUA_VERSION_STRING}") else() message(STATUS " Lua: ${USE_LUA}") diff --git a/src/platform/cmake/FindLuaJIT.cmake b/src/platform/cmake/FindLuaJIT.cmake new file mode 100644 index 00000000000..427b46794c5 --- /dev/null +++ b/src/platform/cmake/FindLuaJIT.cmake @@ -0,0 +1,225 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindLuaJIT +------- + +Locate LuaJIT library. + +This module defines: + +``LUAJIT_FOUND`` + if false, do not try to link to LuaJIT +``LUAJIT_LIBRARIES`` + both luajit and luajitlib +``LUAJIT_INCLUDE_DIR`` + where to find luajit.h +``LUAJIT_VERSION_STRING`` + the version of LuaJIT found +``LUAJIT_VERSION_MAJOR`` + the major version of LuaJIT +``LUAJIT_VERSION_MINOR`` + the minor version of LuaJIT +``LUAJIT_VERSION_PATCH`` + the patch version of LuaJIT +#]=======================================================================] + +cmake_policy(PUSH) # Policies apply to functions at definition-time +cmake_policy(SET CMP0012 NEW) # For while(TRUE) + +unset(_luajit_include_subdirs) +unset(_luajit_library_names) +unset(_luajit_append_versions) + +# this is a function only to have all the variables inside go away automatically +function(_luajit_get_versions) + set(LUAJIT_VERSIONS2 2.1 2.0) + + if (LuaJIT_FIND_VERSION_EXACT) + if (LuaJIT_FIND_VERSION_COUNT GREATER 1) + set(_luajit_append_versions ${LuaJIT_FIND_VERSION_MAJOR}.${LuaJIT_FIND_VERSION_MINOR}) + endif () + elseif (LuaJIT_FIND_VERSION) + # once there is a different major version supported this should become a loop + if (NOT LuaJIT_FIND_VERSION_MAJOR GREATER 2) + if (LuaJIT_FIND_VERSION_COUNT EQUAL 1) + set(_luajit_append_versions ${LUAJIT_VERSIONS2}) + else () + foreach (subver IN LISTS LUAJIT_VERSIONS2) + if (NOT subver VERSION_LESS ${LuaJIT_FIND_VERSION}) + list(APPEND _luajit_append_versions ${subver}) + endif () + endforeach () + # New version -> Search for it (heuristic only! Defines in include might have changed) + if (NOT _luajit_append_versions) + set(_luajit_append_versions ${LuaJIT_FIND_VERSION_MAJOR}.${LuaJIT_FIND_VERSION_MINOR}) + endif() + endif () + endif () + else () + # once there is a different major version supported this should become a loop + set(_luajit_append_versions ${LUAJIT_VERSIONS2}) + endif () + + if (LUAJIT_Debug) + message(STATUS "Considering following LuaJIT versions: ${_luajit_append_versions}") + endif() + + set(_luajit_append_versions "${_luajit_append_versions}" PARENT_SCOPE) +endfunction() + +function(_luajit_set_version_vars) + set(_luajit_include_subdirs_raw "luajit") + + foreach (ver IN LISTS _luajit_append_versions) + string(REGEX MATCH "^([0-9]+)\\.([0-9]+)$" _ver "${ver}") + if (_ver) + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)$" "\\1" _version_major "${ver}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)$" "\\2" _version_minor "${ver}") + list(APPEND _luajit_include_subdirs_raw + luajit${_version_major}${_version_minor} + luajit${_version_major}.${_version_minor} + luajit-${_version_major}.${_version_minor} + ) + endif () + endforeach () + + # Prepend "include/" to each path directly after the path + set(_luajit_include_subdirs "include") + foreach (dir IN LISTS _luajit_include_subdirs_raw) + list(APPEND _luajit_include_subdirs "${dir}" "include/${dir}") + endforeach () + + set(_luajit_include_subdirs "${_luajit_include_subdirs}" PARENT_SCOPE) +endfunction(_luajit_set_version_vars) + +function(_luajit_get_header_version) + unset(LUAJIT_VERSION_STRING PARENT_SCOPE) + set(_hdr_file "${LUAJIT_INCLUDE_DIR}/luajit.h") + + if (NOT EXISTS "${_hdr_file}") + return() + endif () + + file(STRINGS "${_hdr_file}" luajit_version_strings + REGEX "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT [0-9].*") + + string(REGEX REPLACE ".*;#define[ \t]+LUAJIT_VERSION_MAJOR[ \t]+\"([0-9])\"[ \t]*;.*" "\\1" LUAJIT_VERSION_MAJOR ";${luajit_version_strings};") + if (LUAJIT_VERSION_MAJOR MATCHES "^[0-9]+$") + string(REGEX REPLACE ".*;#define[ \t]+LUAJIT_VERSION_MINOR[ \t]+\"([0-9])\"[ \t]*;.*" "\\1" LUAJIT_VERSION_MINOR ";${luajit_version_strings};") + string(REGEX REPLACE ".*;#define[ \t]+LUAJIT_VERSION_RELEASE[ \t]+\"([0-9])\"[ \t]*;.*" "\\1" LUAJIT_VERSION_PATCH ";${luajit_version_strings};") + set(LUAJIT_VERSION_STRING "${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR}.${LUAJIT_VERSION_PATCH}") + else () + string(REGEX REPLACE ".*;#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT ([0-9.]+[^\"]*)\"[ \t]*;.*" "\\1" LUAJIT_VERSION_STRING ";${luajit_version_strings};") + string(REGEX REPLACE "^([0-9]+)\\.[^\"]*$" "\\1" LUAJIT_VERSION_MAJOR "${LUAJIT_VERSION_STRING}") + string(REGEX REPLACE "^[0-9]+\\.([0-9]+)[^\"]*$" "\\1" LUAJIT_VERSION_MINOR "${LUAJIT_VERSION_STRING}") + string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]).*" "\\1" LUAJIT_VERSION_PATCH "${LUAJIT_VERSION_STRING}") + endif () + foreach (ver IN LISTS _luajit_append_versions) + if (ver STREQUAL "${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR}") + set(LUAJIT_VERSION_MAJOR ${LUAJIT_VERSION_MAJOR} PARENT_SCOPE) + set(LUAJIT_VERSION_MINOR ${LUAJIT_VERSION_MINOR} PARENT_SCOPE) + set(LUAJIT_VERSION_PATCH ${LUAJIT_VERSION_PATCH} PARENT_SCOPE) + set(LUAJIT_VERSION_STRING ${LUAJIT_VERSION_STRING} PARENT_SCOPE) + return() + endif () + endforeach () +endfunction(_luajit_get_header_version) + +function(_luajit_find_header) + _luajit_set_version_vars() + + # Initialize as local variable + set(CMAKE_IGNORE_PATH ${CMAKE_IGNORE_PATH}) + while (TRUE) + # Find the next header to test. Check each possible subdir in order + # This prefers e.g. higher versions as they are earlier in the list + # It is also consistent with previous versions of FindLua + foreach (subdir IN LISTS _luajit_include_subdirs) + find_path(LUAJIT_INCLUDE_DIR luajit.h + HINTS ENV LUAJIT_DIR + PATH_SUFFIXES ${subdir} + ) + if (LUAJIT_INCLUDE_DIR) + break() + endif() + endforeach() + # Did not found header -> Fail + if (NOT LUAJIT_INCLUDE_DIR) + return() + endif() + _luajit_get_header_version() + # Found accepted version -> Ok + if (LUAJIT_VERSION_STRING) + if (LUAJIT_Debug) + message(STATUS "Found suitable version ${LUAJIT_VERSION_STRING} in ${LUAJIT_INCLUDE_DIR}/lua.h") + endif() + return() + endif() + # Found wrong version -> Ignore this path and retry + if (LUAJIT_Debug) + message(STATUS "Ignoring unsuitable version in ${LUAJIT_INCLUDE_DIR}") + endif() + list(APPEND CMAKE_IGNORE_PATH "${LUAJIT_INCLUDE_DIR}") + unset(LUAJIT_INCLUDE_DIR CACHE) + unset(LUAJIT_INCLUDE_DIR) + unset(LUAJIT_INCLUDE_DIR PARENT_SCOPE) + endwhile () +endfunction() + +_luajit_get_versions() +_luajit_find_header() +_luajit_get_header_version() +unset(_luajit_append_versions) + +if (LUAJIT_VERSION_STRING) + set(_luajit_library_names + luajit${LUAJIT_VERSION_MAJOR}${LUAJIT_VERSION_MINOR} + luajit${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR} + luajit-${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR} + luajit.${LUAJIT_VERSION_MAJOR}.${LUAJIT_VERSION_MINOR} + lua51 + lua5.1 + lua-5.1 + lua.5.1 + ) +endif () + +find_library(LUAJIT_LIBRARY + NAMES ${_luajit_library_names} luajit + NAMES_PER_DIR + HINTS + ENV LUAJIT_DIR + PATH_SUFFIXES lib +) +unset(_luajit_library_names) + +if (LUAJIT_LIBRARY) + # include the math library for Unix + if (UNIX AND NOT APPLE AND NOT BEOS) + find_library(LUAJIT_MATH_LIBRARY m) + mark_as_advanced(LUAJIT_MATH_LIBRARY) + set(LUAJIT_LIBRARIES "${LUAJIT_LIBRARY};${LUAJIT_MATH_LIBRARY}") + + # include dl library for statically-linked Lua library + get_filename_component(LUAJIT_LIB_EXT ${LUAJIT_LIBRARY} EXT) + if(LUAJIT_LIB_EXT STREQUAL CMAKE_STATIC_LIBRARY_SUFFIX) + list(APPEND LUAJIT_LIBRARIES ${CMAKE_DL_LIBS}) + endif() + + # For Windows and Mac, don't need to explicitly include the math library + else () + set(LUAJIT_LIBRARIES "${LUAJIT_LIBRARY}") + endif () +endif () + +# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJIT + REQUIRED_VARS LUAJIT_LIBRARIES LUAJIT_INCLUDE_DIR + VERSION_VAR LUAJIT_VERSION_STRING) + +mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIBRARY) + +cmake_policy(POP) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index f4f4026e9bf..9ee4771f2da 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -33,6 +33,8 @@ static struct mScriptValue* _luaRootScope(struct mScriptEngineContext*); static bool _luaLoad(struct mScriptEngineContext*, const char*, struct VFile*); static bool _luaRun(struct mScriptEngineContext*); static const char* _luaGetError(struct mScriptEngineContext*); +static void _luaNewMetatable(lua_State*, const char*, const luaL_Reg*); +static const char* _luaTolstring(lua_State*, int, size_t*); static bool _luaCall(struct mScriptFrame*, void* context); @@ -215,6 +217,35 @@ static const int _mScriptSocketNumErrors = sizeof(_mScriptSocketErrors) / sizeof #if LUA_VERSION_NUM < 503 #define lua_pushinteger lua_pushnumber + +static int _luaPairs(lua_State* lua) { + luaL_checkany(lua, 1); + if (luaL_getmetafield(lua, 1, "__pairs") == LUA_TNIL) { + lua_getglobal(lua, "next"); + lua_pushvalue(lua, 1); + lua_pushnil(lua); + } else { + lua_pushvalue(lua, 1); + lua_call(lua, 1, 3); + } + return 3; +} + +static int _luaIpairsIter(lua_State* lua) { + lua_Integer i = luaL_checkinteger(lua, 2) + 1; + lua_pushinteger(lua, i); + lua_pushvalue(lua, -1); + lua_gettable(lua, 1); + return (lua_type(lua, -1) == LUA_TNIL) ? 1 : 2; +} + +static int _luaIpairs(lua_State* lua) { + luaL_checkany(lua, 1); + lua_pushcfunction(lua, _luaIpairsIter); + lua_pushvalue(lua, 1); + lua_pushinteger(lua, 0); + return 3; +} #endif #ifndef LUA_OK @@ -388,33 +419,21 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS luaL_openlibs(luaContext->lua); - luaL_newmetatable(luaContext->lua, "mSTStruct"); -#if LUA_VERSION_NUM < 502 - luaL_register(luaContext->lua, NULL, _mSTStruct); -#else - luaL_setfuncs(luaContext->lua, _mSTStruct, 0); -#endif - lua_pop(luaContext->lua, 1); - - luaL_newmetatable(luaContext->lua, "mSTTable"); -#if LUA_VERSION_NUM < 502 - luaL_register(luaContext->lua, NULL, _mSTTable); -#else - luaL_setfuncs(luaContext->lua, _mSTTable, 0); -#endif - lua_pop(luaContext->lua, 1); - - luaL_newmetatable(luaContext->lua, "mSTList"); -#if LUA_VERSION_NUM < 502 - luaL_register(luaContext->lua, NULL, _mSTList); -#else - luaL_setfuncs(luaContext->lua, _mSTList, 0); -#endif - lua_pop(luaContext->lua, 1); + _luaNewMetatable(luaContext->lua, "mSTStruct", _mSTStruct); + _luaNewMetatable(luaContext->lua, "mSTTable", _mSTTable); + _luaNewMetatable(luaContext->lua, "mSTList", _mSTList); lua_getglobal(luaContext->lua, "require"); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); +#if LUA_VERSION_NUM < 503 + lua_pushcfunction(luaContext->lua, _luaIpairs); + lua_setglobal(luaContext->lua, "ipairs"); + + lua_pushcfunction(luaContext->lua, _luaPairs); + lua_setglobal(luaContext->lua, "pairs"); +#endif + lua_pushliteral(luaContext->lua, "log"); lua_pushcclosure(luaContext->lua, _luaPrintShim, 1); lua_setglobal(luaContext->lua, "print"); @@ -976,11 +995,19 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi // Make the old _ENV the __index in the metatable lua_newtable(luaContext->lua); lua_pushliteral(luaContext->lua, "__index"); +#if LUA_VERSION_NUM >= 502 lua_getupvalue(luaContext->lua, -4, 1); +#else + lua_getfenv(luaContext->lua, -4); +#endif lua_rawset(luaContext->lua, -3); lua_pushliteral(luaContext->lua, "__newindex"); +#if LUA_VERSION_NUM >= 502 lua_getupvalue(luaContext->lua, -4, 1); +#else + lua_getfenv(luaContext->lua, -4); +#endif lua_rawset(luaContext->lua, -3); lua_setmetatable(luaContext->lua, -2); @@ -1006,7 +1033,11 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi } lua_rawset(luaContext->lua, -3); +#if LUA_VERSION_NUM >= 502 lua_setupvalue(luaContext->lua, -2, 1); +#else + lua_setfenv(luaContext->lua, -2); +#endif luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); return true; case LUA_ERRSYNTAX: @@ -1032,6 +1063,39 @@ const char* _luaGetError(struct mScriptEngineContext* context) { return luaContext->lastError; } +void _luaNewMetatable(lua_State* lua, const char* name, const luaL_Reg* funcs) { + luaL_newmetatable(lua, name); +#if LUA_VERSION_NUM < 502 + lua_pushstring(lua, name); + lua_setfield(lua, -2, "__name"); + luaL_register(lua, NULL, funcs); +#else + luaL_setfuncs(lua, funcs, 0); +#endif + lua_pop(lua, 1); +} + +const char* _luaTolstring(lua_State* lua, int idx, size_t* len) { +#if LUA_VERSION_NUM < 503 + if (!luaL_getmetafield(lua, idx, "__tostring")) { + if (luaL_getmetafield(lua, idx, "__name")) { + const char *name = lua_tostring(lua, -1); + lua_pop(lua, 1); + lua_pushfstring(lua, "%s: %p", name, lua_topointer(lua, idx)); + return lua_tolstring(lua, -1, len); + } + } else { + lua_pop(lua, 1); + } + lua_getglobal(lua, "tostring"); + lua_pushvalue(lua, idx); + lua_call(lua, 1, 1); + return lua_tolstring(lua, -1, len); +#else + return luaL_tolstring(lua, idx, len); +#endif +} + bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, lua_State* lua, struct mScriptList* frame) { bool ok = true; if (frame) { @@ -1589,7 +1653,7 @@ static int _luaPrintShim(lua_State* lua) { // Until then, stringify and concatenate: for (int i = 0; i < n; i++) { - luaL_tolstring(lua, i * 2 + 3, NULL); + _luaTolstring(lua, i * 2 + 3, NULL); lua_replace(lua, i * 2 + 3); if (i == 0) { lua_pushliteral(lua, "");