diff --git a/README.md b/README.md index 95dae741..35dd5526 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,18 @@ In the browser `require` and `package.loadlib` try to find a file by making sync ### _Missing_ features -- `lua_gc`/`collectgarbage`: Fengari relies on the JS garbage collector and does not implement its own. +- `lua_gc`/`collectgarbage`: These functions only work when the JS garbage collector is available in the current environment: + - `LUA_GCCOLLECT` + - Supported in IE via `CollectGarbage` + - Supported in Opera < 15 via `opera.collect` + - Only supported in v8 engines (e.g. node.js, Chrome) if the process was started with `--expose-gc` + - `LUA_GCSTEP` + - Only supported in v8 engines (e.g. node.js, Chrome) if the process was started with `--expose-gc` + - `LUA_GCCOUNT`, `LUA_GCCOUNTB` + - Supported in WebKit browsers via `performance.memory` + - Supported in node.js via the [`v8` library](https://nodejs.org/api/v8.html) + - `LUA_GCSTOP`, `LUA_GCSETPAUSE`, `LUA_GCSETSTEPMUL` + - Supported in node.js via the [`v8` library](https://nodejs.org/api/v8.html) - The following functions are only available in Node: - The entire `io` lib - `os.remove` diff --git a/src/defs.js b/src/defs.js index daa5f702..17f287cb 100644 --- a/src/defs.js +++ b/src/defs.js @@ -327,6 +327,16 @@ const LUA_OPEQ = 0; const LUA_OPLT = 1; const LUA_OPLE = 2; +const LUA_GCSTOP = 0; +const LUA_GCRESTART = 1; +const LUA_GCCOLLECT = 2; +const LUA_GCCOUNT = 3; +const LUA_GCCOUNTB = 4; +const LUA_GCSTEP = 5; +const LUA_GCSETPAUSE = 6; +const LUA_GCSETSTEPMUL = 7; +const LUA_GCISRUNNING = 9; + const LUA_MINSTACK = 20; const { LUAI_MAXSTACK } = require('./luaconf.js'); @@ -379,6 +389,16 @@ const LUA_MASKRET = (1 << LUA_HOOKRET); const LUA_MASKLINE = (1 << LUA_HOOKLINE); const LUA_MASKCOUNT = (1 << LUA_HOOKCOUNT); + +module.exports.LUA_GCCOLLECT = LUA_GCCOLLECT; +module.exports.LUA_GCCOUNT = LUA_GCCOUNT; +module.exports.LUA_GCCOUNTB = LUA_GCCOUNTB; +module.exports.LUA_GCISRUNNING = LUA_GCISRUNNING; +module.exports.LUA_GCRESTART = LUA_GCRESTART; +module.exports.LUA_GCSETPAUSE = LUA_GCSETPAUSE; +module.exports.LUA_GCSETSTEPMUL = LUA_GCSETSTEPMUL; +module.exports.LUA_GCSTEP = LUA_GCSTEP; +module.exports.LUA_GCSTOP = LUA_GCSTOP; module.exports.LUA_HOOKCALL = LUA_HOOKCALL; module.exports.LUA_HOOKCOUNT = LUA_HOOKCOUNT; module.exports.LUA_HOOKLINE = LUA_HOOKLINE; diff --git a/src/lapi.js b/src/lapi.js index 7443fb78..3d253e89 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -1,6 +1,15 @@ "use strict"; const { + LUA_GCCOLLECT, + LUA_GCCOUNT, + LUA_GCCOUNTB, + LUA_GCISRUNNING, + LUA_GCRESTART, + LUA_GCSETPAUSE, + LUA_GCSETSTEPMUL, + LUA_GCSTEP, + LUA_GCSTOP, LUA_MULTRET, LUA_OPBNOT, LUA_OPEQ, @@ -1101,8 +1110,91 @@ const lua_upvaluejoin = function(L, fidx1, n1, fidx2, n2) { ref1.f.upvals[ref1.i] = up2; }; -// This functions are only there for compatibility purposes -const lua_gc = function () {}; +const lua_gc = function (L, what, data) { + switch (what) { + case LUA_GCSTOP: { + if (typeof process !== "undefined") { + /* we can't stop the v8 GC */ + /* but we can slow it down to effectivly 0... */ + require("v8").setFlagsFromString("--gc_interval="+0xFFFFFFFF); + return 0; + } + return -1; + } + case LUA_GCRESTART: { + return -1; + } + case LUA_GCCOLLECT: { + if (typeof gc === "function") { + /* v8 when started with --expose-gc */ + /* global gc */ + gc(false); + } else if (typeof CollectGarbage === "function") { + /* IE */ + /* global CollectGarbage */ + CollectGarbage(); + } else if (typeof opera !== "undefined" && typeof opera.collect === "function") { + /* global opera */ + opera.collect(); + } else { + return -1; + } + return 0; + } + case LUA_GCCOUNT: { + /* GC values are expressed in Kbytes: #bytes/2^10 */ + let v; + if (typeof process !== "undefined") { + v = require("v8").getHeapStatistics().used_heap_size; + } else if (typeof performance !== "undefined" && typeof performance.memory === "object") { + v = performance.memory.usedJSHeapSize; + } else { + return -1; + } + return v >> 10; + } + case LUA_GCCOUNTB: { + let v; + if (typeof process !== "undefined") { + v = require("v8").getHeapStatistics().used_heap_size; + } else if (typeof performance !== "undefined" && typeof performance.memory === "object") { + v = performance.memory.usedJSHeapSize; + } else { + return -1; + } + return v & 0x3ff; + } + case LUA_GCSTEP: { + if (typeof gc === "function") { + /* global gc */ + gc(true); + return 0; + } + return -1; + } + case LUA_GCSETPAUSE: { + if (typeof process !== "undefined") { + require("v8").setFlagsFromString("--gc_interval="+data); + /* we can't get the current gc_interval */ + return 0; + } + return -1; + } + case LUA_GCSETSTEPMUL: { + if (typeof process !== "undefined") { + require("v8").setFlagsFromString("--max_old_space_size="+data); + return 0; + } + return -1; + } + case LUA_GCISRUNNING: { + /* we can't stop the JS GC */ + /* we can't get the current gc_interval */ + return 1; + } + default: return -1; /* invalid option */ + } +}; const lua_getallocf = function () { console.warn("lua_getallocf is not available"); diff --git a/src/lbaselib.js b/src/lbaselib.js index 63754efe..7cf1588d 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -1,6 +1,15 @@ "use strict"; const { + LUA_GCCOLLECT, + LUA_GCCOUNT, + LUA_GCCOUNTB, + LUA_GCISRUNNING, + LUA_GCRESTART, + LUA_GCSETPAUSE, + LUA_GCSETSTEPMUL, + LUA_GCSTEP, + LUA_GCSTOP, LUA_MULTRET, LUA_OK, LUA_TFUNCTION, @@ -15,6 +24,7 @@ const { lua_callk, lua_concat, lua_error, + lua_gc, lua_getglobal, lua_geti, lua_getmetatable, @@ -33,6 +43,7 @@ const { lua_pushinteger, lua_pushliteral, lua_pushnil, + lua_pushnumber, lua_pushstring, lua_pushvalue, lua_rawequal, @@ -200,10 +211,30 @@ const opts = [ "count", "step", "setpause", "setstepmul", "isrunning" ].map((e) => to_luastring(e)); +const optsnum = [ + LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, + LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, + LUA_GCISRUNNING +]; const luaB_collectgarbage = function(L) { - luaL_checkoption(L, 1, "collect", opts); - luaL_optinteger(L, 2, 0); - luaL_error(L, to_luastring("lua_gc not implemented")); + let o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; + let ex = luaL_optinteger(L, 2, 0); + let res = lua_gc(L, o, ex); + if (res == -1) + return luaL_error(L, to_luastring("unavailable in current environment")); + switch (o) { + case LUA_GCCOUNT: { + let b = lua_gc(L, LUA_GCCOUNTB, 0); + lua_pushnumber(L, res + b/1024); + return 1; + } + case LUA_GCSTEP: case LUA_GCISRUNNING: + lua_pushboolean(L, res); + return 1; + default: + lua_pushinteger(L, res); + return 1; + } }; const luaB_type = function(L) { diff --git a/src/lua.js b/src/lua.js index 4389c597..f621bec1 100644 --- a/src/lua.js +++ b/src/lua.js @@ -13,6 +13,15 @@ module.exports.LUA_ERRGCMM = defs.thread_status.LUA_ERRGCMM; module.exports.LUA_ERRMEM = defs.thread_status.LUA_ERRMEM; module.exports.LUA_ERRRUN = defs.thread_status.LUA_ERRRUN; module.exports.LUA_ERRSYNTAX = defs.thread_status.LUA_ERRSYNTAX; +module.exports.LUA_GCCOLLECT = defs.LUA_GCCOLLECT; +module.exports.LUA_GCCOUNT = defs.LUA_GCCOUNT; +module.exports.LUA_GCCOUNTB = defs.LUA_GCCOUNTB; +module.exports.LUA_GCISRUNNING = defs.LUA_GCISRUNNING; +module.exports.LUA_GCRESTART = defs.LUA_GCRESTART; +module.exports.LUA_GCSETPAUSE = defs.LUA_GCSETPAUSE; +module.exports.LUA_GCSETSTEPMUL = defs.LUA_GCSETSTEPMUL; +module.exports.LUA_GCSTEP = defs.LUA_GCSTEP; +module.exports.LUA_GCSTOP = defs.LUA_GCSTOP; module.exports.LUA_HOOKCALL = defs.LUA_HOOKCALL; module.exports.LUA_HOOKCOUNT = defs.LUA_HOOKCOUNT; module.exports.LUA_HOOKLINE = defs.LUA_HOOKLINE;